Flutter is not rebuilding same widget with different parameters - dart

I was working with bottom navigation with similar child widgets in which only parameters are changed. The problem only happens when widgets are of StatefulWidget else there is no problem, indications in bottomnavbar is changing but not the body.
Child 1:
Child 2:
Actual result:
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;
Widget body;
#override
void initState() {
// body = getBody(0);
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
elevation: 0,
),
body: body,
bottomNavigationBar: BottomNavigationBar(
currentIndex: _counter,
onTap: (index){
_counter = index;
setState(() {
body = getBody(index);
});
},items: [
BottomNavigationBarItem(icon: Icon(Icons.language),title:
Text('HELLO')),
BottomNavigationBarItem(icon: Icon(Icons.security),title:
Text('BYE'))
]),
);
}
Widget getBody(int pos){
if(pos==0){
// return new Mx(category: 'ALPHA',type: '#',);
return new MyTAbs(category: 'ALPHA',type: '#',);
}
else{
// return new Mx(category:'BETA',type: '#',);
return new MyTAbs(category:'BETA',type: '#',);
}
}
}
class Mx extends StatelessWidget{
final String type,category;
Mx({this.type,this.category});
#override
Widget build(BuildContext context) {
return new Scaffold(
backgroundColor: getColor(),
body: new Center(
child: Text(category+' '+type),
),
);
}
Color getColor(){
if(category=='ALPHA'){
return Colors.red;
}
else{
return Colors.green;
}
}
}
class MyTAbs extends StatefulWidget{
final String type,category;
MyTAbs({this.type,this.category});
Tabs createState() => new Tabs(title: category,type: type);
}
class Tabs extends State<MyTAbs>{
final String title,type;
Tabs({this.title,this.type});
#override
Widget build(BuildContext context) {
// TODO: implement build
return new Scaffold(
backgroundColor: getColor(),
appBar: AppBar(
title: Text(title+' '+type),
),
);
}
Color getColor(){
if(title=='ALPHA'){
return Colors.red;
}
else{
return Colors.green;
}
}
}
and I can't use statelessWidget because there's a dynamic tab section inside.

Solved this issue by adding new Key as parameter and passed a UniqueKey
like
return new MyTAbs(category: 'ALPHA',type: '#',key: UniqueKey(),);
MyTAbs class
class MyTAbs extends StatefulWidget{
final String type,category;
final Key key;
MyTAbs({#required this.key,this.type,this.category});
Tabs createState() => new Tabs(title: category,type: type,key: key);
}
Tabs class
class Tabs extends State<MyTAbs>{
final String title,type;
final Key key;
Tabs({this.title,this.type,#required this.key});
#override
#override
Widget build(BuildContext context) {
// TODO: implement build
return new Scaffold(
backgroundColor: getColor(),
appBar: AppBar(
title: Text(title+' '+type),
),
);
}
Color getColor(){
if(title=='ALPHA'){
return Colors.red;
}
else{
return Colors.green;
}
}
}
little about Key
You can use keys to control which widgets the framework matches up with other widgets when a widget rebuilds. By default, the framework matches widgets in the current and previous build according to their runtimeType and the order in which they appear. With keys, the framework requires that the two widgets have the same key as well as the same runtimeType. more in flutter docs

Change your Tabs class
class Tabs extends State<MyTAbs> {
#override
Widget build(BuildContext context) {
return new Scaffold(
backgroundColor: getColor(),
appBar: AppBar(
title: Text(widget.category + ' ' + widget.type),
),
);
}
Color getColor() {
if (widget.category == 'ALPHA') {
return Colors.red;
} else {
return Colors.green;
}
}
}
Class State (Tabs) created only once. So after that you can't call constructor with new parameters. But you have access to the widgets' fields

your problem in "MyTAbs" passing parameters class
after edit it , now its work
you dont need to pass the date from "Stateful" class to the "State", just call it with "widget.parameterName" in the state
your code after edit :
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;
Widget body;
#override
void initState() {
// body = getBody(0);
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
elevation: 0,
),
body: body,
bottomNavigationBar: BottomNavigationBar(
currentIndex: _counter,
onTap: (index){
_counter = index;
setState(() {
body = getBody(index);
});
},items: [
BottomNavigationBarItem(icon: Icon(Icons.language),title:
Text('HELLO')),
BottomNavigationBarItem(icon: Icon(Icons.security),title:
Text('BYE'))
]),
);
}
Widget getBody(int pos){
if(pos==0){
// return new Mx(category: 'ALPHA',type: '#',);
return new MyTAbs(category: 'ALPHA',type: '#',);
}
else{
// return new Mx(category:'BETA',type: '#',);
return new MyTAbs(category:'BETA',type: '#',);
}
}
}
class Mx extends StatelessWidget{
final String type,category;
Mx({this.type,this.category});
#override
Widget build(BuildContext context) {
return new Scaffold(
backgroundColor: getColor(),
body: new Center(
child: Text(category+' '+type),
),
);
}
Color getColor(){
if(category=='ALPHA'){
return Colors.red;
}
else{
return Colors.green;
}
}
}
class MyTAbs extends StatefulWidget{
final String type,category;
MyTAbs({this.type,this.category});
Tabs createState() => new Tabs();
}
class Tabs extends State<MyTAbs>{
#override
Widget build(BuildContext context) {
print(widget.type);
// TODO: implement build
return new Scaffold(
backgroundColor: getColor(),
appBar: AppBar(
title: Text(widget.category+' '+widget.type),
),
);
}
Color getColor(){
if(widget.category=='ALPHA'){
return Colors.red;
}
else{
return Colors.green;
}
}
}

Related

Flutter - RepaintBoundary causes state reset of StatefulWidget

I have a preview widget that loads data after a user tap. This state (already tapped or not) should not be lost while scrolling (the preview is located in a list) or navigating through other screen.
The scrolling is solved by adding AutomaticKeepAliveClientMixin which saves the state when scrolling away.
Now i also need to wrap the preview widget (actually a more complex widget that contains the preview) with a RepaintBoundary, to be able to make a "screenshot" of this widget alone.
Before i wrap the widget with a RepaintBoundary, the state is saved both while scrolling and navigating to another screen.
After i add the RepaintBoundary the scrolling still works but for navigation the state is reset.
How can i wrap a Stateful widget that should hold its state with a RepaintBoundary?
Code is a simplified example of my implementation with the same problem.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
MyApp({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
final title = 'Test';
return MaterialApp(
title: title,
home: Scaffold(
appBar: AppBar(
title: Text(title),
),
body: TestList(40),
),
);
}
}
class TestList extends StatefulWidget {
final int numberOfItems;
TestList(this.numberOfItems);
#override
_TestListState createState() => _TestListState();
}
class _TestListState extends State<TestList> {
#override
Widget build(BuildContext context) {
print('_TestListState build.');
return ListView.builder(
itemCount: widget.numberOfItems,
itemBuilder: (context, index) {
return RepaintBoundary(
key: GlobalKey(),
child: Preview()
);
},
);
}
}
class Preview extends StatefulWidget {
#override
_PreviewState createState() => _PreviewState();
}
class _PreviewState extends State<Preview> with AutomaticKeepAliveClientMixin {
bool loaded;
#override
void initState() {
super.initState();
print('_PreviewState initState.');
loaded = false;
}
#override
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
super.build(context);
print('_PreviewState build.');
if(loaded) {
return GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => NewScreen()),
);
},
child: ListTile(
title: Text('Loaded. Tap to navigate.'),
leading: Icon(Icons.visibility),
),
);
} else {
return GestureDetector(
onTap: () {
setState(() {
loaded = true;
});
},
child: ListTile(
title: Text('Tap to load.'),
),
);
}
}
}
class NewScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('New Screen')),
body: Center(
child: Text(
'Navigate back and see if loaded state is gone.',
style: TextStyle(fontSize: 14.0),
),
),
);
}
}
Take a look at RepaintBoundary.wrap, it assigns the RepaintBoundary widget a key based on its child or childIndex so state is maintained:
class _TestListState extends State<TestList> {
#override
Widget build(BuildContext context) {
print('_TestListState build.');
return ListView.builder(
itemCount: widget.numberOfItems,
itemBuilder: (context, index) {
return RepaintBoundary.wrap(
Preview(),
index,
);
},
);
}
}
https://api.flutter.dev/flutter/widgets/RepaintBoundary/RepaintBoundary.wrap.html
EDIT: As per the below comments, it looks like this solution would break the screenshot ability so you'd have to store the list of children widgets in your state like so:
class _TestListState extends State<TestList> {
List<Widget> _children;
#override
void initState() {
super.initState();
_children = List.generate(
widget.numberOfItems,
(_) => RepaintBoundary(
key: GlobalKey(),
child: Preview(),
));
}
#override
Widget build(BuildContext context) {
print('_TestListState build.');
return ListView(children: _children);
}
}

Flutter: how to access ScopeModel properties in child pages

I am trying to understand ScopeModel in Flutter and need some help on how access values from the model on a different page
My home page has a bottom navigation bar and when click just display the search page. I have wrap the widget tree with the ScopeModel and added the model.
The count is getting incremented but I am not sure how to access it from the search 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> {
ScopeCounter sc;
#override
void initState() {
super.initState();
sc = new ScopeCounter();
}
final List<Widget> _children = [
..
Search()
];
var _currentIndex = 0;
#override
Widget build(BuildContext context) {
return ScopedModel(
model:sc ,
child: Scaffold(
appBar: AppBar(
title: Center(child: Text("test")),
),
drawer: JobsDrawer(),
body: _children[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
onTap: onTabTapped, // new
currentIndex: _currentIndex, // new
items: [
new BottomNavigationBarItem(
icon: new Icon(Icons.search),
title: new Text("search"),
)
])),
);
}
void onTabTapped(int index) {
setState(() {
sc.increment();
print(sc.counter1.count);
_currentIndex = index;
});
}
}
This my Model
class ScopeCounter extends Model {
Counter counter1 = Counter();
increment() {
counter1.count += 1;
}
}
class Counter {
int count = 1;
}
Search page
class Search extends StatefulWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
...
}
}
I would like access the count from the "search" page.
Thanks for your help
You just have to wrap your SearchPage's Widget (Scaffold in this case) with a ScopedModelDescendant Widget. This gives you access to your ScopedModel.
A great explanation can be found in the documentation: https://flutter.dev/docs/development/data-and-backend/state-mgmt/simple
EDIT: Also your ScopedModel must be a parent of both: MyHomePage and Search.

Single Instance of Widget class is used even when different variables are coded to be used

I want to use BottomNavigationBar of Flutter so for that I have created a class called BaseWidget which will be changed as the user taps the item.
class BaseWidget extends StatefulWidget {
final String title;
BaseWidget(this.title);
_BaseWidgetState createState() => _BaseWidgetState(this.title);
}
class _BaseWidgetState extends State<BaseWidget> {
final String title;
_BaseWidgetState(this.title);
#override
Widget build(BuildContext context) {
return Center(child: Text(title));
}
}
In the above class am returning the Center widget with child as Text widget.
class HomeWidget extends StatefulWidget {
_HomeWidgetState createState() => _HomeWidgetState();
}
class _HomeWidgetState extends State<HomeWidget> {
int pageIndex = 0;
final _home = BaseWidget('Home');
final _business = BaseWidget('Business');
final _school = BaseWidget('School');
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Bottom Navigation Bar'),
),
body: choosePager(),
bottomNavigationBar: BottomNavigationBar(
currentIndex: pageIndex,
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home), title: Text('Home')),
BottomNavigationBarItem(
icon: Icon(Icons.business), title: Text('Business')),
BottomNavigationBarItem(
icon: Icon(Icons.school), title: Text('School')),
],
onTap: onTap,
),
),
);
}
void onTap(int index) {
setState(() {
this.pageIndex = index;
});
}
Widget choosePager() {
switch (pageIndex) {
case 0:
return _home;
break;
case 1:
return _business;
break;
case 2:
return _school;
break;
default:
return Text('Unknown');
break;
}
}
}
Problem 1:
Whenever user taps on the BottomNavigationBarItem the text should change to the respected string passed in the BaseWidget's constructor. But it only shows Home and the rest 2 are ignored.
Problem 2:
I am planning to replace Center widget with the ListView widget to populate the list of Schools and Businesses which will be fetched from the network API in paginated way. So I don't want to reinitialise the classes again when BottomNavigationBarItem is tapped as that would result in loss of data which is already fetched. To prevent data lose I am declaring _home, _business & _school property and using these property in choosePager() method.
There are several issues with your code:
1- The real problem is that you never rebuild the BaseWidget. You construct 3 new BaseWidgets, but you only ever call the build of the _home widget, because it's the first one returned by choosePager(). Since you don't create _home, _business, _school in the HomeWidget build, no other BaseWidget can ever get built.
2- When you don't need to store any state/variables for a widget, use a Stateless widget.
3- Don't do anything in the constructor of your State. Use initState https://docs.flutter.io/flutter/widgets/State/initState.html for that instead.
4- Create widgets using const constructors when possible.
5- Widget constructor take named parameters. One of those should be the key. Use super to call the base constructor.
With that in mind, this is what the code should look like:
class BaseWidget extends StatelessWidget {
final String title;
const BaseWidget({Key key, this.title}) : super(key: key);
#override
Widget build(BuildContext context) {
return Center(
child: Text(title),
);
}
}
class HomeWidget extends StatefulWidget {
_HomeWidgetState createState() => _HomeWidgetState();
}
class _HomeWidgetState extends State<HomeWidget> {
int pageIndex = 0;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Bottom Navigation Bar'),
),
body: choosePager(),
bottomNavigationBar: BottomNavigationBar(
currentIndex: pageIndex,
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text('Home'),
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
title: Text('Business'),
),
BottomNavigationBarItem(
icon: Icon(Icons.school),
title: Text('School'),
),
],
onTap: onTap,
),
),
);
}
void onTap(int index) {
setState(() {
pageIndex = index;
});
}
Widget choosePager() {
Widget result;
switch (pageIndex) {
case 0:
result = BaseWidget(title: 'Home');
break;
case 1:
result = BaseWidget(title: 'Business');
break;
case 2:
result = BaseWidget(title: 'School');
break;
default:
result = Text('Unknown');
break;
}
return result;
}
}
Edit: For your example, you may want to fetch some data from the network and only use the widget to display it. In that case, create a new class (not a Widget) to fetch & hold on to the data, and use the Widget only for displaying the data.
Some sample code:
/// Silly class to fetch data
class DataClass {
static int _nextDatum = 0;
int _data;
DataClass();
Future<int> fetchData() async {
await Future.delayed(Duration(
milliseconds: 2000,
));
_data = _nextDatum++;
return _data;
}
int getData() {
return _data;
}
}
class BaseClass extends StatefulWidget {
final String title;
final DataClass data;
const BaseClass({Key key, this.title, this.data}) : super(key: key);
#override
_BaseClassState createState() => _BaseClassState();
}
class _BaseClassState extends State<BaseClass> {
String title;
DataClass data;
#override
Widget build(BuildContext context) {
String dataStr = data == null ? ' - ' : '${data.getData()}';
return Center(
child: Text(
'$title: $dataStr',
),
);
}
#override
void initState() {
super.initState();
// initState gets called only ONCE
title = widget.title;
data = widget.data;
}
#override
void didUpdateWidget(BaseClass oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.data != oldWidget.data) {
data = widget.data;
}
if (widget.title != oldWidget.title) {
title = widget.title;
}
}
}
class HomeWidget extends StatefulWidget {
_HomeWidgetState createState() => _HomeWidgetState();
}
class _HomeWidgetState extends State<HomeWidget> {
int pageIndex = 0;
Map<String, DataClass> _dataMap = <String, DataClass>{};
#override
void initState() {
super.initState();
_init().then((result) {
// Since we need to rebuild the widget with the resulting data,
// make sure to use `setState`
setState(() {
_dataMap = result;
});
});
}
Future<Map<String, DataClass>> _init() async {
// this fetches the data only once
return <String, DataClass>{
'home': DataClass()..fetchData(),
'business': DataClass()..fetchData(),
'school': DataClass()..fetchData(),
};
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Bottom Navigation Bar'),
),
body: choosePager(),
bottomNavigationBar: BottomNavigationBar(
currentIndex: pageIndex,
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text('Home'),
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
title: Text('Business'),
),
BottomNavigationBarItem(
icon: Icon(Icons.school),
title: Text('School'),
),
],
onTap: onTap,
),
),
);
}
void onTap(int index) {
setState(() {
pageIndex = index;
});
}
Widget choosePager() {
Widget result;
switch (pageIndex) {
// it doesn't matter if you create a new BaseClass() a hundred times, flutter is optimized enough to not care. The `initState()` gets called only once. You're fetching the data only once.
case 0:
result = BaseClass(
title: 'Home',
data: _dataMap['home'],
);
break;
case 1:
result = BaseClass(
title: 'Business',
data: _dataMap['business'],
);
break;
case 2:
result = BaseClass(
title: 'School',
data: _dataMap['school'],
);
break;
default:
result = Text('Unknown');
break;
}
return result;
}
}
After lot of RND I solved my problem using IndexedStack. It shows the single Child from the list of children based on the index.
It will initialise all the children when the Widget build(BuildContext context) method of the _HomeWidgetState is called. So any time you switch the tabs, the object won't be reinitialised.
Here is my full code
class BaseWidget extends StatefulWidget {
final String title;
BaseWidget(this.title);
_BaseWidgetState createState() => _BaseWidgetState();
}
class _BaseWidgetState extends State<BaseWidget> {
#override
Widget build(BuildContext context) {
return Center(child: Text(widget.title));
}
}
class HomeWidget extends StatefulWidget {
_HomeWidgetState createState() => _HomeWidgetState();
}
class _HomeWidgetState extends State<HomeWidget> {
int _pageIndex = 0;
List<Widget> _children;
#override
void initState() {
super.initState();
_children = [
BaseWidget('Home'),
BaseWidget('Business'),
BaseWidget('School')
];
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Bottom Navigation Bar'),
),
body: IndexedStack(
children: _children,
index: _pageIndex,
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _pageIndex,
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home), title: Text('Home')),
BottomNavigationBarItem(
icon: Icon(Icons.business), title: Text('Business')),
BottomNavigationBarItem(
icon: Icon(Icons.school), title: Text('School')),
],
onTap: onTap,
),
),
);
}
void onTap(int index) {
setState(() {
_pageIndex = index;
});
}
}
Please try this code, I have edited the BaseWidget class
class BaseWidget extends StatefulWidget {
final String title;
BaseWidget(this.title);
_BaseWidgetState createState() => _BaseWidgetState();
}
class _BaseWidgetState extends State<BaseWidget> {
#override
Widget build(BuildContext context) {
return Center(child: Text(widget.title));
}
}

How to change a State of a StatefulWidget inside a StatelessWidget?

Just testing out flutter. The code sample below is a very simple flutter app. The problem is that I don't know how to call the setState() function inside the TestTextState class in order to change the text each time when the change button is pressed.
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Test app',
home: new Scaffold(
appBar: new AppBar(
title: new Text("Test"),
),
body: new Test(),
),
);
}
}
class Test extends StatelessWidget {
final TestText testText = new TestText();
void change() {
testText.text == "original" ? testText.set("changed") : testText.set("original");
}
#override
Widget build(BuildContext context) {
return new Column(
children: [
testText,
new RaisedButton(
child: new Text("change"),
onPressed: () => change(),
),
]
);
}
}
class TestText extends StatefulWidget {
String text = "original";
void set(String str) {
this.text = str;
}
#override
TestTextState createState() => new TestTextState();
}
class TestTextState extends State<TestText> {
#override
Widget build(BuildContext context) {
return new Text(this.widget.text);
}
}
I have approached this problem by initializing the _TestTextState as the final property of the TestText widget which allows to simply update the state when the change button is pressed. It seems like a simple solution but I'm not sure whether it's a good practice.
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Test app',
home: new Scaffold(
appBar: new AppBar(
title: new Text("Test"),
),
body: new Test(),
),
);
}
}
class Test extends StatelessWidget {
final _TestText text = new _TestText();
#override
Widget build(BuildContext context) {
return new Column(
children: [
text,
new RaisedButton(
child: new Text("change"),
onPressed: () => text.update(),
),
]
);
}
}
class TestText extends StatefulWidget {
final _TestTextState state = new _TestTextState();
void update() {
state.change();
}
#override
_TestTextState createState() => state;
}
class _TestTextState extends State<TestText> {
String text = "original";
void change() {
setState(() {
this.text = this.text == "original" ? "changed" : "original";
});
}
#override
Widget build(BuildContext context) {
return new Text(this.text);
}
}
thier is no way to do so. any how you have to convert your StatelessWidget to StatefulWidget.
Solution based on your existing code
class Test extends StatelessWidget {
final StreamController<String> streamController = StreamController<String>.broadcast();
#override
Widget build(BuildContext context) {
final TestText testText = TestText(streamController.stream);
return new Column(children: [
testText,
new RaisedButton(
child: Text("change"),
onPressed: () {
String text = testText.text == "original" ? "changed" : "original";
streamController.add(text);
},
),
]);
}
}
class TestText extends StatefulWidget {
TestText(this.stream);
final Stream<String> stream;
String text = "original";
#override
TestTextState createState() => new TestTextState();
}
class TestTextState extends State<TestText> {
#override
void initState() {
widget.stream.listen((str) {
setState(() {
widget.text = str;
});
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Text(widget.text);
}
}
But it's not the best idea - to use non-final field inside Stateful Widget
P.S.
You can also use this - scoped_model

Flutter close a Dialog inside a condition

I am trying to close a Dialog dynamically.
What I am actually trying to do is to change the content of the dialog depending on the information I have at the moment.
Starts with loading info and no button and after a few seconds could be an error with the OK button to close the Dialog Box.
class Dialogs{
loginLoading(BuildContext context, String type, String description){
var descriptionBody;
if(type == "error"){
descriptionBody = CircleAvatar(
radius: 100.0,
maxRadius: 100.0,
child: new Icon(Icons.warning),
backgroundColor: Colors.redAccent,
);
} else {
descriptionBody = new Center(
child: new CircularProgressIndicator(),
);
}
return showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context){
return AlertDialog(
title: descriptionBody,
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
Center(child: Text(description))
],
),
),
);
}
);
}
}
So after creating the instance os the dialog and opening it
Dialogs _dialog = new Dialogs();
_dialog.loginLoading(context, "loading", "loading...");
// Close the dialog code here
don't know how to do it
// Call again the AlertDialog with different content.
https://docs.flutter.io/flutter/material/showDialog.html
The dialog route created by this method is pushed to the root navigator. If the application has multiple Navigator objects, it may be necessary to call Navigator.of(context, rootNavigator: true).pop(result) to close the dialog rather than just Navigator.pop(context, result).
So any one of the below should work for you
Navigator.of(context, rootNavigator: true).pop(result)
Navigator.pop(context, result)
You don't need to close and reopen the dialog. Instead let flutter handle the dialog update. The framework is optimised for just that.
Here is a working example app that you can use as a starting point (just add your own Dialogs class):
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'MyApp',
home: Login(
child: Home(),
),
);
}
}
class Home extends StatefulWidget {
final Dialogs dialog = Dialogs();
#override
State<StatefulWidget> createState() => HomeState();
}
class HomeState extends State<Home> {
#override
void didChangeDependencies() {
super.didChangeDependencies();
Future.delayed(Duration(milliseconds: 50)).then((_) {
widget.dialog.loginLoading(
context,
LoginStateProvider.of(context).type,
LoginStateProvider.of(context).description,
);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Updating Dialog'),
),
body: Container(),
);
}
}
class Login extends StatefulWidget {
final Widget child;
Login({#required this.child});
#override
State<StatefulWidget> createState() => LoginState();
}
class LoginState extends State<Login> {
String type = 'wait';
String description = 'foo';
#override
void didChangeDependencies() {
super.didChangeDependencies();
Future.delayed(Duration(milliseconds: 2000)).then((_) {
setState(() {
type = 'error';
description = 'bar';
});
});
}
#override
Widget build(BuildContext context) {
return LoginStateProvider(widget.child, type, description);
}
}
class LoginStateProvider extends InheritedWidget {
final String type;
final String description;
LoginStateProvider(Widget child, this.type, this.description)
: super(child: child);
#override
bool updateShouldNotify(LoginStateProvider old) {
return type != old.type || description != old.description;
}
static LoginStateProvider of(BuildContext context) =>
context.inheritFromWidgetOfExactType(LoginStateProvider);
}

Resources