Cannot Find Method 'SetState' within widget - drop-down-menu

I have a widget that is called in my main scaffolding file later. This widget contains a dropdown menu but, I cannot change the state when selecting another value. The field does not update and I get the error message 'error: Method not found: 'setState'.
setState(() {'
^^^^^^^^
I have updated the setState method and removed code from it but, it still says the method not found.
child: DropdownButton(
hint: Text('Medical'),
value: _selectedCustomerType,
onChanged: (newValue) {
setState(() {
_selectedCustomerType = newValue;
});
},
items: _customerType.map((cusType) {
print(cusType);
return DropdownMenuItem(
child: Text(cusType),
value: cusType,
);
}).toList(),
),
I need to be able to update the value and display it when the new value is chosen.

You can't use setState outside of a StatefulWidget so you should wrap your DropdownButton in a StatefulWidget, for example:
class StatefulDropdownButton extends StatefulWidget {
final List<String> _customerType;
StatefulDropdownButton(this._customerType);
#override
State<StatefulWidget> createState() => DropdownButtonState();
}
class DropdownButtonState extends State<StatefulDropdownButton> {
String _selectedCustomerType;
#override
Widget build(BuildContext context) {
return DropdownButton(
hint: Text('Medical'),
value: _selectedCustomerType,
onChanged: (newValue) {
setState(() {
_selectedCustomerType = newValue;
});
},
items: widget._customerType.map((cusType) {
print(cusType);
return DropdownMenuItem(
child: Text(cusType),
value: cusType,
);
}).toList(),
);
}
}

SetState is not accessible inside the main method, and neither inside function, to make it accessible , you need to create a Stateful class and exactly in the State class , because actually your widget is a statefull class : it changes its state everytime the user make an event..

Related

Flutter - How to animate NavigationRail page transition [windows]

I have implemented the NavigationRail as such:
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
HomePageState createState() => HomePageState();
}
class HomePageState extends State<HomePage> {
int _selectedIndex = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: [
NavigationRail(
destinations: const [
NavigationRailDestination(
icon: Icon(Icons.home), label: Text("Home")),
NavigationRailDestination(
icon: Icon(Icons.settings), label: Text("Settings"))
],
selectedIndex: _selectedIndex,
),
Expanded(child: pageBuilder())
],
),
);
}
Widget pageBuilder() {
switch (_selectedIndex) {
case 0:
return const _HomePage();
case 1:
return const _SettingsPage();
default:
return const _HomePage();
}
}
}
With _HomePage :
class _HomePage extends StatefulWidget {
const _HomePage({Key? key}) : super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<_HomePage> {
#override
Widget build(BuildContext context) {
return Container(child: Text("HomePage"));
}
And SettingsPage is the same but says "SettingsPage" instead.
The question is, how can I animate the transition between these pages?
I cannot use Route and call Navigator.of(context).push(_pageRouter()) under the switch statement as it will throw errors about building or such (it's a long one which I can provide if needed).
Is there any way to achieve this without using Route? or some workaround?
There are multiple animation type. You can use switcher to change between animation
here is the link for widget. it will automatically change widget by animation.
AnimatedSwitcher(
duration: const Duration(milliseconds: 500),
transitionBuilder: (Widget child, Animation<double> animation) {
return ScaleTransition(scale: animation, child: child);
},
child: pageBuilder(),
)

How to show in a ListView a Tree of Strings?

I have a Map<String, dynamic>, where dynamic is a dynamic tree of Strings. I need to show in a ListView (or another Widget that allow to show a Tree structure) the Strings and their children. Each String is a collapsable item (if has children) of the ListView. For example:
Text
text
text
text
text
text
text
text
text
this is the code of an example of the tree:
void main() {
Map<String, dynamic> joinTrees(int level, int maxLevel,
List<List<String>> trees, Map<String, dynamic> tmpMap) {
if (maxLevel < 0) return {};
List<String> nodes = trees.map((tree) => tree[level]).toSet().toList();
print(nodes);
for (String node in nodes) {
List<List<String>> childrenBranchs =
trees.where((tree) => tree[level] == node).toList();
if (childrenBranchs.length == 1 &&
childrenBranchs[0][childrenBranchs[0].length - 1] == node) {
print("leaf: " + node);
tmpMap[node] = null;
} else {
Map<String, dynamic> childrenTree = joinTrees(
level + 1, maxLevel, childrenBranchs, new Map<String, dynamic>());
print("node: " + node);
tmpMap[node] = childrenTree;
}
}
return tmpMap;
}
List<List<String>> trees = [
["A", "A11", "A21"],
["A", "A12"],
["A", "A11", "A22"],
["B", "B11", "B21"],
["C"]
];
int maxLength = trees
.map((tree) => tree.length)
.toList()
.reduce((curr, next) => curr > next ? curr : next);
int maxLevel = maxLength - 1;
print(trees);
print(maxLength);
Map<String, dynamic> joinedTree =
joinTrees(0, maxLevel, trees, new Map<String, dynamic>());
print(joinedTree);
}
this is de ouput of joinedTree:
{
A:
{
A11:
{
A21: null,
A22: null
},
A12: null
},
B:
{
B11:
{
B21: null
}
},
C: null
}
if your tree look like this, detail Entry class please reference full code
final List<Entry> data = <Entry>[
Entry(
'A',
<Entry>[
Entry(
'A11',
<Entry>[
Entry('A21'),
Entry('A22'),
],
),
Entry('A12'),
],
),
Entry(
'B',
<Entry>[
Entry(
'B11',
<Entry>[
Entry('B21'),
],
),
],
),
Entry(
'C',
),
];
inside ListView.builder, build item with
Widget _buildTiles(Entry root) {
if (root.children.isEmpty) {
return Padding(
padding: const EdgeInsets.only(left: 10.0),
child: ListTile(title: Text(root.title)),
);
}
return Padding(
padding: const EdgeInsets.only(left: 10.0),
child: ExpansionTile(
key: PageStorageKey<Entry>(root),
title: Text(root.title),
children: root.children.map(_buildTiles).toList(),
),
);
}
full code
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
),
home: ExpansionTileSample(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
});
}
#override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: Center(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: Column(
// Column is also a layout widget. It takes a list of children and
// arranges them vertically. By default, it sizes itself to fit its
// children horizontally, and tries to be as tall as its parent.
//
// Invoke "debug painting" (press "p" in the console, choose the
// "Toggle Debug Paint" action from the Flutter Inspector in Android
// Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
// to see the wireframe for each widget.
//
// Column has various properties to control how it sizes itself and
// how it positions its children. Here we use mainAxisAlignment to
// center the children vertically; the main axis here is the vertical
// axis because Columns are vertical (the cross axis would be
// horizontal).
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class ExpansionTileSample extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('ExpansionTile'),
),
body: ListView.builder(
itemBuilder: (BuildContext context, int index) =>
EntryItem(data[index]),
itemCount: data.length,
),
),
);
}
}
// One entry in the multilevel list displayed by this app.
class Entry {
Entry(this.title, [this.children = const <Entry>[]]);
final String title;
final List<Entry> children;
}
// The entire multilevel list displayed by this app.
final List<Entry> data = <Entry>[
Entry(
'A',
<Entry>[
Entry(
'A11',
<Entry>[
Entry('A21'),
Entry('A22'),
],
),
Entry('A12'),
],
),
Entry(
'B',
<Entry>[
Entry(
'B11',
<Entry>[
Entry('B21'),
],
),
],
),
Entry(
'C',
),
];
// Displays one Entry. If the entry has children then it's displayed
// with an ExpansionTile.
class EntryItem extends StatelessWidget {
const EntryItem(this.entry);
final Entry entry;
Widget _buildTiles(Entry root) {
if (root.children.isEmpty) {
return Padding(
padding: const EdgeInsets.only(left: 10.0),
child: ListTile(title: Text(root.title)),
);
}
return Padding(
padding: const EdgeInsets.only(left: 10.0),
child: ExpansionTile(
key: PageStorageKey<Entry>(root),
title: Text(root.title),
children: root.children.map(_buildTiles).toList(),
),
);
}
#override
Widget build(BuildContext context) {
return _buildTiles(entry);
}
}

Flutter: bloc, how to show an alert dialog

I´m new in the bloc pattern and stream stuff. I want to show up an alert dialog when I press a button, but I can´t find a way to do it. Actually my code is:
Widget button() {
return RaisedButton(
child: Text('Show alert'),
color: Colors.blue[700],
textColor: Colors.white,
onPressed: () {
bloc.submit();
});
}
return Scaffold(
appBar: AppBar(
title: Text("Title"),
),
body: StreamBuilder(
stream: bloc.getAlert,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text("I have Dataaaaaa ${snapshot.data}");
} else
return ListView(
children: <Widget>[
Container(
button()
)
...
And the BLoC:
final _submissionController = StreamController();
Stream get submissionStream=> _submissionController.stream;
Sink get submissionSink=> _submissionController.sink;
I tried to do something like:
Widget button() {
return StreamBuilder(
stream: submissionStream
builder: (context, snapshot){
if (snapshot.hasData){
return showDialog(...)
}else
return RaisedButton(
child: Text('Show alert'),
color: Colors.blue[700],
textColor: Colors.white,
onPressed: () {
bloc.submit();
});
}
But, of course, it didn´t work.
You can't show a dialog when build working. When you have new data, then you create a new widget. Probably better for you will be not using the stream in this case, but if it necessary you should use
WidgetsBinding.instance.addPostFrameCallback((_) => yourFunction(context));
or
Future.microtask(() => showDialogFunction(context));
in your if
if (snapshot.hasData) {
WidgetsBinding.instance.addPostFrameCallback((_) => showDialogFunction(context));
}
This code will be launched after build method, so dialog will show immediately.
Bloc function always return widget, so always return button() or different wiget when stream has data
You can use BlocListener for showing Dialogs, Snackbars or for navigating to a new page.
With this approach you may want to refactor to rely on the bloc state rather than accessing the stream directly.
Listener is guaranteed to only be called once for each state change, however builder can be called many times. Also you can't do some operations on builders, such as navigating to another page.
return Scaffold(
appBar: AppBar(
title: Text("Title"),
),
body: BlocProvider<YourBloc>(
create: () => YourBloc(),
child: Stack([
SnackbarManager(),
YourScreen(),
]),
),
);
...
/// This is basically an empty UI widget that only
/// manages the snackbar
class SnackbarManager extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocListener<YourBloc, YourBlocState>(
listener: (context, state) {
if (state.hasMyData) {
Scaffold.of(context).showSnackBar(SnackBar(
content:
Text("I got data"),
));
}
},
child: Container(),
);
}
}
I know I'm late to the party, but maybe this will help someone.
I'm currently learning about BLoC myself and ran into a similar problem.
First of all, I want to recommend the flutter_bloc package from pub.dev.
It contains Widgets that help you with this like BlocListener and BlocConsumer.
If you want to go without it, you could try using a StatefulWidget and listen to it separately and use your logic to show the dialog. (also make sure your stream is broadcasting as in my example, so it can have multiple listeners)
I've made an example which you could copy-past into dartpad.dev/flutter:
import 'dart:async';
import 'package:flutter/material.dart';
final myStream = StreamController<bool>.broadcast();
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark(),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
initState() {
super.initState();
myStream.stream.listen((show){
if(show)
showDialog(
barrierDismissible: false,
context: context,
builder: (context) {
return AlertDialog(
title: Text('MyDialog'),
actions: [
TextButton(
child: Text('Close'),
onPressed: (){
myStream.sink.add(false);
}),
]
);
}
);
if(!show) {
Navigator.pop(context);
}
});
}
#override
Widget build(BuildContext context) {
return Center(child: ElevatedButton(
child: Text('Show Alert'),
onPressed: (){
myStream.sink.add(true);
}));
}
}
Here is what I did, it might be wrong as I'm also new to flutter. But works for my scenario.
Widget build(BuildContext context) {
final authBloc = BlocProvider.of<AuthBloc>(context);
authBloc.outServerResponse.listen((serverResponse) {
if (serverResponse.status == 'success') {
_navigateToLogin();
} else {
_showSnakBar(serverResponse.message);
}
});
.... Rest of the code which returns the widget,
which in my case is form widget with button for submitting as follows,
onPressed: () {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
authBloc.processRegister.add(_registrationData.toMap());
}
}
outServerResponse is the stream that outputs after finishing API POST call.
authBloc.processRegister is the input sink to pass form data to my Auth API Service Provider.
_nagivateToLogin & _showSnakBar are simple functions
_navigateToLogin() {
Navigator.of(context).pop();
}
_showSnakBar(String msg) {
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text(msg),
),
);
}
this process working for me.
I called my Dialog before return the widget
Future.microtask(() => showLoginSuccess(BuildContext context));
If you're using flutter_bloc package which I suggest to use, you should use the provided BlocListener widget which listens to state changes and could execute logic codes. like this for example:
BlocListener<BlocA, BlocAState>(
listener: (context, state) {
// do stuff here based on BlocA's state
},
child: Container(),
);
but if you also need the build widget, you should use BlocConsumer widget which has the listener and the builder at the same time:
BlocConsumer<BlocA, BlocAState>(
listener: (context, state) {
// do stuff here based on BlocA's state
},
builder: (context, state) {
// return widget here based on BlocA's state
}
);
It's common to show a dialog without changing the build widget,
so BlocConsumer offers the buildWhen option for this situation which takes the previous and current states to decide about the builder:
buildWhen: (state, currentState){
if (state is MainComplexTableState && currentState is NewComplexRegistration) {
return false;
}
if (state is ErrorToShowUp) {
return false;
}
return true;
},
I solved it by maintaining two context as follows
**
BlocProvider of type A ==>widget class B(showdialog(context:context,builder(context2){
Blocprvider.value(value:Blocprovider.of<A>.context)
child:BlocListener(
listner(context2,state)
{//
your works
//}
child:AlertDialog( some widgets
a button function ()=> context.read<A>().function or property name
//
1.here we call old context in fact it is registered with provider, 2. context2 is only for building a new builder widget.
3.hence we get bloc passed through a navigation and accessible in navigated alert widget without creating it

Flutter: How can I prevent default behaviour on key press?

I'm trying to intercept when a user presses the volume buttons to perform a specific action and prevent the default behaviour (volume changes).
This is the code I have so far:
RawKeyboard.instance.addListener(_keyboardListener);
void _keyboardListener(RawKeyEvent e) {
if(e.runtimeType == RawKeyUpEvent) {
RawKeyEventDataAndroid eA = e.data;
if(eA.keyCode == 24) { //volume up key
_goNextPage();
}
if(eA.keyCode == 25) { //volume down key
_goPrevPage();
}
}
}
How would I go about preventing the volume from changing (and stopping the volume slider from appearing at the top)?
A Javascript analogous would be calling event.preventDefault() on the key event.
This seems to be a rather trivial matter, but I haven't been able to find any answers in the docs.
Thanks.
I've faced a similar problem and what to share how I solved it.
To stop the propagation we have to return true from onKey method of a FocusNode in the focus nodes tree. To achieve this I've wrapped my app body with FocusScope and Focus widgets like this:
MaterialApp(
home: Scaffold(
body: FocusScope(
autofocus: true,
child: Focus(
autofocus: true,
canRequestFocus: true,
onKey: (data, event) {
if (event.isKeyPressed(LogicalKeyboardKey.audioVolumeUp)) {
print("Volume up");
return true;
}
if (event
.isKeyPressed(LogicalKeyboardKey.audioVolumeDown)) {
print("Volume down");
return true;
}
return false;
},
child: Text(text: "Hallochen")))))
Thanks to Sergey's answer I was able to solve the issue as well. In my case, I wanted to create a ListView, with pull to refresh (RefreshIndicator) that will work for both mobile devices and web.
I tried to implement a refresh indicator which will appear when the user clicks F5 to refresh the web page, but I had to prevent the browser from actually refreshing the page.
Here's an example of my implementation, which prevents refresh from occuring when the user clicks F5.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class ExamplePage extends StatefulWidget {
#override
_ExamplePageState createState() => _ExamplePageState();
}
class _ExamplePageState extends State<ExamplePage> {
final GlobalKey<AnimatedListState> listKey = GlobalKey<AnimatedListState>();
final GlobalKey<RefreshIndicatorState> _refreshIndicatorKey = new GlobalKey<RefreshIndicatorState>();
List items = [];
Future<void> _pullRefresh() async {
await Future.delayed(Duration(milliseconds: 1000));
}
#override
Widget build(BuildContext context) {
return FocusScope(
autofocus: true,
child: Focus(
autofocus: true,
canRequestFocus: true,
onKey: (data, event) {
if (event
.isKeyPressed(LogicalKeyboardKey.f5)) {
_refreshIndicatorKey.currentState!.show();
return KeyEventResult.handled;
}
return KeyEventResult.ignored;
},
child: Container(
padding: EdgeInsets.all(15.0),
child: RefreshIndicator(
key: _refreshIndicatorKey,
onRefresh: _pullRefresh,
child: AnimatedList(
key: listKey,
initialItemCount: items.length,
itemBuilder: (context, index, animation) {
return _buildItem(context, index, animation);
},
),
),
),
),
);
}
Widget _buildItem(
BuildContext context, int index, Animation<double> animation) {
return Text("Example");
}
}
all the solutions here are for Focus widget, they inspired me but were not quite the solution for me as I was using RawKeyboardListener and did not wanna change to something else.
here is what worked for me:
final node = FocusNode();
final fnode = FocusScopeNode();
#override
Widget build(BuildContext context) {
return FocusScope(
node: fnode,
child: RawKeyboardListener(
focusNode: node,
...
),
);
}

Flutter - setState Not Using New Data

I have a screen which I pass data back to like so:
final myUpdatedObject = await Navigator.of(context).push(...);
setState({
object = myUpdatedObject;
});
Having checked with a simple print at all places in my widget body that my object is used, the new data is present after it is passed back by the Navigator and setState is called.
However, when the widget is rebuilt, even though the new data is apparently there, it is not reflected in the UI changes, it shows old data.
Is this some sort of caching in debug mode? Whats causing this issue?
The example below starts with a Map named textMessageMap with a message key that populates a Text Widget with 'Home'. Tap the FloatingActionButton and you'll navigate to SecondScreen. If you tap the 'Go back!' button in SecondScreen, the message key in textMessageMap will be updated to read 'Second Screen'. If you tap the back button on the Scaffold of SecondScreen, textMessageMap will be nulled out. Calling setState updates the UI appropriately. See if your implementation is different.
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Map<String, String> textMessageMap = {'message': 'Home'};
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text(
'${textMessageMap != null ? textMessageMap['message'] : 'map is null'}',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: new FloatingActionButton(
onPressed: () {
_launchSecondScreen();
},
child: new Icon(Icons.add),
),
);
}
_launchSecondScreen() async {
final value = await Navigator.push(
context,
MaterialPageRoute<Map<String, String>>(
builder: (BuildContext _) => SecondScreen()));
setState(() {
textMessageMap = value;
});
}
}
class SecondScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Screen"),
),
body: Center(
child: RaisedButton(
onPressed: () {
// Navigate back to the first screen by popping the current route
// off the stack. The text 'Second Screen' will replace 'Home'.
// If you hit the scaffold's back button, the return value will be
// null instead.
final map = {'message': 'Second Screen'};
Navigator.pop(context, map);
},
child: Text('Go back!'),
),
),
);
}
}

Resources