flutter: provider dosen't work in statefulwidget - flutter

I create a new flutter demo and modify it to use the provider package. But it doesn't work. And here is my code.
class MyState {
MyState();
int cnt = 0;
void increase() {
print("increase. $cnt");
cnt++;
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: Provider<MyState>(
create: (_) => MyState(),
child: MyHomePage(title: 'Flutter Demo Home Page'),
),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Consumer<MyState>(
builder: (context, state, _) {
return Text(
"${state.cnt}",
style: Theme.of(context).textTheme.headline4,
);
},
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: Provider.of<MyState>(context, listen: false).increase,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
When press the button, the UI is not rebuilt. And as the printed messages show, the cnt field of Mystate had been changed. Why? May provider can not be used in statefulwidget?

Provider: You can use Provider to provide a value anywhere in the widget tree. It will not rebuild the widget tree whenever the value changes. It simply passes the model to its descendant's widget in the widget tree.
ChangeNotifierProvider: ChangeNotifierProvider listens for changes in the model object. It rebuilds the dependents widgets whenever ChangeNotifier.notifyListeners is called.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(),
);
}
}
class MyState with ChangeNotifier {
MyState();
int cnt = 0;
void increase() {
print("increase. $cnt");
cnt++;
notifyListeners();
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<MyState>(
create: (context) => MyState(),
child: Scaffold(
appBar: AppBar(
title: Text("Page Title"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'You have pushed the button this many times:',
),
Consumer<MyState>(
builder: (context, counter, child) => Text(
'${counter.cnt}',
style: Theme.of(context).textTheme.display1,
),
),
],
),
),
floatingActionButton: Builder(builder: (context) {
return FloatingActionButton(
onPressed: Provider.of<MyState>(context, listen: false).increase,
tooltip: 'Increment',
child: Icon(Icons.add),
);
}),
),
);
}
}

You can copy paste run full code below
Step 1: MyState extends ChangeNotifier and use notifyListeners()
Step 2: Use ChangeNotifierProvider
code snippet
class MyState extends ChangeNotifier {
MyState();
int cnt = 0;
void increase() {
print("increase. $cnt");
cnt++;
notifyListeners();
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
...
home: ChangeNotifierProvider(
create: (_) => MyState(),
child: MyHomePage(title: 'Flutter Demo Home Page'),
working demo
full code
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(MyApp());
}
class MyState extends ChangeNotifier {
MyState();
int cnt = 0;
void increase() {
print("increase. $cnt");
cnt++;
notifyListeners();
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: ChangeNotifierProvider(
create: (_) => MyState(),
child: MyHomePage(title: 'Flutter Demo Home Page'),
),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Consumer<MyState>(
builder: (context, state, _) {
return Text(
"${state.cnt}",
style: Theme.of(context).textTheme.headline4,
);
},
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: Provider.of<MyState>(context, listen: false).increase,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}

Related

Flutter: How to use Markdown in showDialog?

I'm trying to use Markdown (from package flutter_markdown: ^0.6.6) in a dialog box. Following example returns error:
RenderShrinkWrappingViewport does not support returning intrinsic
dimensions.
This is my code:
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.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(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Package flutter_markdown'),
),
body: Center(
child: HelpButtonWithDialog(
title: 'markdown test',
content: '# Help Text\n\n* Text line 1\n*Text Line 2',
),
),
);
}
}
class HelpButtonWithDialog extends StatelessWidget {
final String title;
final String content;
HelpButtonWithDialog({
required this.title,
required this.content,
});
#override
Widget build(BuildContext context) {
return IconButton(
icon: Icon(Icons.help_outline),
onPressed: () => showDialog(
barrierDismissible: true,
context: context,
builder: (BuildContext ctx) => AlertDialog(
title: Text(title),
content: Markdown(
data: content,
shrinkWrap: true,
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text('OK'),
)
],
),
),
);
}
}
I found a workaround by using Dialog and copying parts from AlertDialog. I also filed an enhancement request https://github.com/flutter/flutter/issues/89464 to the Flutter team to get this proper.
Here is the workaround code:
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.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(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Package flutter_markdown'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'flutter_markdown: Please add example how to use in showDialog'),
Text('https://github.com/flutter/flutter/issues/89464'),
HelpButtonWithDialog(
title: 'markdown test',
content: '# Help Text\n\n* Text line 1\n* Text Line 2',
),
],
),
),
);
}
}
class HelpButtonWithDialog extends StatelessWidget {
final String title;
final String content;
HelpButtonWithDialog({
required this.title,
required this.content,
});
#override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final DialogTheme dialogTheme = DialogTheme.of(context);
return IconButton(
icon: Icon(Icons.help_outline),
onPressed: () => showDialog(
barrierDismissible: true,
context: context,
builder: (BuildContext ctx) => Dialog(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: EdgeInsets.fromLTRB(24.0, 24.0, 24.0, 24.0),
child: DefaultTextStyle(
style:
dialogTheme.titleTextStyle ?? theme.textTheme.headline6!,
child: Text(title),
),
),
MarkdownBody(
data: content,
shrinkWrap: true,
),
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text('OK'),
),
],
),
),
),
);
}
}

Flutter Provider.of<> without a consumer don't change my state

I am trying to get into the provider topic, however calling a function only works if I put it into a consumer
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => ClickerProvider()),
],
child: Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Text("some text"),
),
floatingActionButton: FloatingActionButton(
onPressed: () => Provider.of<ClickerProvider>(context, listen: false)
.incrementCounter(),
tooltip: 'Increment',
child: Icon(Icons.add),
),
));
}
As in this example, my state is not updated. However, it already works with a consumer.
floatingActionButton: Consumer<ClickerProvider>(
builder: (context, value, child) {
return FloatingActionButton(
onPressed: Provider.of<ClickerProvider>(context, listen: false)
.incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
);
},
)
Is there an error in my code?
You can copy paste run two full code below
Reason : Can not find ClickerProvider
Solution 1: Move ClickerProvider to upper level such as MyApp
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => ClickerProvider()),
],
child: MaterialApp(
Solution 2: Use Builder
body: Center(child: Builder(builder: (BuildContext context) {
return Text(context.watch<ClickerProvider>().getCounter.toString());
})),
floatingActionButton: Builder(builder: (BuildContext context) {
return FloatingActionButton(
onPressed: () =>
Provider.of<ClickerProvider>(context, listen: false)
.incrementCounter(),
tooltip: 'Increment',
child: Icon(Icons.add),
);
full code 1
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class ClickerProvider extends ChangeNotifier {
int _count = 0;
int get getCounter {
return _count;
}
void incrementCounter() {
_count += 1;
notifyListeners();
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => ClickerProvider()),
],
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
));
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Text(context.watch<ClickerProvider>().getCounter.toString()),
),
floatingActionButton: FloatingActionButton(
onPressed: () => Provider.of<ClickerProvider>(context, listen: false)
.incrementCounter(),
tooltip: 'Increment',
child: Icon(Icons.add),
));
}
}
full code 2
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class ClickerProvider extends ChangeNotifier {
int _count = 0;
int get getCounter {
return _count;
}
void incrementCounter() {
_count += 1;
notifyListeners();
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => ClickerProvider()),
],
child: Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(child: Builder(builder: (BuildContext context) {
return Text(context.watch<ClickerProvider>().getCounter.toString());
})),
floatingActionButton: Builder(builder: (BuildContext context) {
return FloatingActionButton(
onPressed: () =>
Provider.of<ClickerProvider>(context, listen: false)
.incrementCounter(),
tooltip: 'Increment',
child: Icon(Icons.add),
);
}),
));
}
}
As you can refer in the source code of Consumer here:
Obtains [Provider] from its ancestors and passes its value to [builder].
The [Consumer] widget doesn't do any fancy work. It just calls [Provider.of]
in a new widget, and delegates its build implementation to [builder].
Provider.of<X> depends on value of listen (true or false) to trigger new State.build() to widgets and State.didChangeDependencies() for StatefulWidget.
Consumer<X> always update UI, as it uses Provider.of<T>(context), where listen is true
In this case, since your listen is set as false, but you're putting it in the Consumer which make it true. That's why the UI will update with Consumer

Why is Flutter NotificationListener not catching my notifications?

I am having a problem with the NotificationListener in flutter. I've build a simple testing app because I am struggling with it.
After clicking on the FlatButton the Notification should be dispatched and then caught by the NotificationListener in onNotification.
So the expected console output would be:
"TestNot"
"Bubble"
But all I am getting is "TestNot".
So the notification is not caught by the listener.
Any idea what I could be doing wrong?
Thank you :-)
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyNotification extends Notification {
final String title;
const MyNotification({this.title});
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: NotificationListener<MyNotification>(
onNotification: (notification) {
print("Bubble");
return true;
},
child: Center(
child: Column(
// horizontal).
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
FlatButton(onPressed: () {print("TestNot"); MyNotification(title: "TestNot").dispatch(context);}, child: Text("TestNot")),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
)),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
When you need a child to notify its parent, you can use NotificationListener.
But, when you need communications to be implemented inversely, in other words, a parent to notify its children, you can use ValueListenableBuilder
A nice doc about it available here:
https://medium.com/flutter-community/flutter-notifications-bubble-up-and-values-go-down-c1a499d22d5f
"Flutter, notifications ‘bubble up’ and values ‘go down’"
You cannot receive the notification at the same level of where it was dispatched. Please refer to docs : https://api.flutter.dev/flutter/widgets/NotificationListener-class.html
NotificationListener class :
A widget that listens for Notifications bubbling up the tree.
I've updated your code to make it work.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyNotification extends Notification {
final String title;
const MyNotification({this.title});
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: NotificationListener<MyNotification>(
onNotification: (MyNotification notification) {
print("Bubble");
return true;
},
child: Center(
child: Column(
// horizontal).
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
MyChild(),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
)),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class MyChild extends StatelessWidget {
#override
Widget build(BuildContext context) {
return FlatButton(
onPressed: () {
print("TestNot");
MyNotification(title: "TestNot").dispatch(context);
},
child: Text("TestNot"));
}
}

Flutter/Dart: A TextEditingController was used after being disposed

Please, someone, help on this, I am not sure if this is a framework glitch then how are there not more post on this and if it is me then how come there is not much on this error!
===========================
main.dart
import 'package:flutter/material.dart';
import 'dialog_reusable.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
showDialog( context: context, builder: (context) { return MyDialog(); });
},
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
===========================
dialog_reusable.dart
import 'package:flutter/material.dart';
import 'dialog_reusable.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
showDialog( context: context, builder: (context) { return MyDialog(); });
},
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
===========================
===========================
Steps to Reproduce
The Textfield is part of a Dialog() along with 2 buttons 'ok' and 'cancel'
Create a new Flutter project with standard options
Remove the files in LIB folder
Make new files with the code and name provided above
When the Dialog() pops up and the 'cancel' button is clicked the following error happens.
════════ Exception caught by widgets library ════════
The following assertion was thrown building MouseRegion(listeners: [enter, exit], state: _MouseRegionState#1877d):
A TextEditingController was used after being disposed.
Once you have called dispose() on a TextEditingController, it can no longer be used.
The relevant error-causing widget was:
TextField file:///C:/MobileApps/Apps/Clima-Flutter/lib/utilities/mydialog.dart:90:15
When the exception was thrown, this was the stack:
#0 ChangeNotifier._debugAssertNotDisposed. (package:flutter/src/foundation/change_notifier.dart:106:9)
#1 ChangeNotifier._debugAssertNotDisposed (package:flutter/src/foundation/change_notifier.dart:112:6)
#2 ChangeNotifier.removeListener (package:flutter/src/foundation/change_notifier.dart:167:12)
#3 _AnimatedState.didUpdateWidget (package:flutter/src/widgets/transitions.dart:159:28)
#4 StatefulElement.update (package:flutter/src/widgets/framework.dart:4690:58)
Steps Tried:
Disable the Textfield before Dispose() by using a variable in 'enable' property of Textfield
Assign NULL to 'Controller' property of TextField if the variable that holds enable property for TextField is false before Dispose(), via the ternary operator and if clause
Assign NULL to 'onChanged:' property of TextField if the variable that holds enable property for TextField is false before Dispose(), via the ternary operator and if clause
To prevent getting the error Flutter/Dart: A TextEditingController was used after being disposed, the previously disposed TextEditingController shouldn't be used again. One way that you can do here is pass a new instance of TextEditingController to be used in the AlertDialog, or depending on how you use a TextEditingController.
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var textEditingController = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Text(
'You have entered ${textEditingController.text}',
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => alertDialog(textEditingController),
tooltip: 'Increment',
child: const Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
alertDialog(TextEditingController textEditingController) {
return showDialog<String>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: TextField(
controller: textEditingController,
decoration: const InputDecoration(hintText: 'Enter Something'),
),
actions: <Widget>[
TextButton(
child: const Text(
"Cancel",
style: TextStyle(color: Colors.black),
),
onPressed: () => Navigator.of(context).pop(),
),
TextButton(
child: const Text(
"OK",
style: TextStyle(color: Colors.red),
),
onPressed: () {
setState(() {
// Triggers a Widget rebuild to update textEditingController state
});
Navigator.of(context).pop();
},
),
],
);
},
);
}
}

How to keep the widget's state in Scaffold.drawer in Flutter?

I want to keep the widget's state in Scaffold.drawer. The Scaffold.drawer is a custom widget, which has a RaiseButton in it.
When click the button, the text in the button changed.
But when the drawer is closed, and reopen the drawer, the changed text is reseted.
I have use " with AutomaticKeepAliveClientMixin<> " in my custom Drawer, but it does't work.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Flutter Demo"),
),
drawer: Drawer(child: CustomDrawer(),),
body: Center(
child: Text("Flutter Demo"),
),
);
}
}
class CustomDrawer extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _CustomDrawerState();
}
}
class _CustomDrawerState extends State<CustomDrawer> with AutomaticKeepAliveClientMixin<CustomDrawer> {
String btnText = "Click!";
#override
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
super.build(context);
return Center(
child: RaisedButton(onPressed: () {
setState(() {
btnText = "Clicked!!";
});
}, child: Text(btnText),),
);
}
}
I expect the widget's state can keep, even if the Drawer is closed.
Create a separate widget for the drawer and just use in anywhere you need to.
Manage the Drawer State with a Provider
class DrawerStateInfo with ChangeNotifier {
int _currentDrawer = 0;
int get getCurrentDrawer => _currentDrawer;
void setCurrentDrawer(int drawer) {
_currentDrawer = drawer;
notifyListeners();
}
void increment() {
notifyListeners();
}
}
Adding State Management to the Widget tree
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MultiProvider(
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.teal,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
),
providers: <SingleChildCloneableWidget>[
ChangeNotifierProvider<DrawerStateInfo>(
builder: (_) => DrawerStateInfo()),
],
);
}
}
Creating The Drawer Widget for reuse in application
class MyDrawer extends StatelessWidget {
MyDrawer(this.currentPage);
final String currentPage;
#override
Widget build(BuildContext context) {
var currentDrawer = Provider.of<DrawerStateInfo>(context).getCurrentDrawer;
return Drawer(
child: ListView(
children: <Widget>[
ListTile(
title: Text(
"Home",
style: currentDrawer == 0
? TextStyle(fontWeight: FontWeight.bold)
: TextStyle(fontWeight: FontWeight.normal),
),
trailing: Icon(Icons.arrow_forward),
onTap: () {
Navigator.of(context).pop();
if (this.currentPage == "Home") return;
Provider.of<DrawerStateInfo>(context).setCurrentDrawer(0);
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (BuildContext context) =>
MyHomePage(title: "Home")));
},
),
ListTile(
title: Text(
"About",
style: currentDrawer == 1
? TextStyle(fontWeight: FontWeight.bold)
: TextStyle(fontWeight: FontWeight.normal),
),
trailing: Icon(Icons.arrow_forward),
onTap: () {
Navigator.of(context).pop();
if (this.currentPage == "About") return;
Provider.of<DrawerStateInfo>(context).setCurrentDrawer(1);
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (BuildContext context) => MyAboutPage()));
},
),
],
),
);
}
}
Use of Drawer in one of your pages
class MyAboutPage extends StatefulWidget {
#override
_MyAboutPageState createState() => _MyAboutPageState();
}
class _MyAboutPageState extends State<MyAboutPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('About Page'),
),
drawer: MyDrawer("About"),
);
}
}
In your case, you have 2 choices:
You should keep your state in your Top level widget. in your case _MyHomePageState;
Use state managers like Redux, Bloc, ScopedModel. I think ScopedModel is great for you in this case.
otherwise, you can't control the state of Drawer. cause it re-creates every moment you call the Drawer by the action button in Appbar;