Flutter: Android: How to call setState() from another file? - flutter

For applying app's setting configuration to take effect around app i need to trigger main's setState from appSettings file, how to do so?
Files code:
for main.dart
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Builder(
builder: (context) => Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(isVibrationEnabled
? "Vibration is enabled"
: "Vibration is disabled"),
MaterialButton(
color: Colors.grey,
child: Text("Open app setting"),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AppSettings(),
),
);
},
)
],
),
),
),
),
);
for globalVariables.dart
bool isVibrationEnabled = false;
for appSettings.dart
class _AppSettingsState extends State<AppSettings> {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: FlatButton(
color: Colors.grey,
child: Text(
isVibrationEnabled ? "Disable Vibration" : "Enable Vibration"),
onPressed: () {
setState(() {
isVibrationEnabled
? isVibrationEnabled = false
: isVibrationEnabled = true;
});
//What to do here to trigger setState() in main.dart flie
//for displaying "Vibration is enabled" or "Vibration is disabled"
//acording to the value of bool variable which is in globalVariable.dart file.
},
),
),
),
);
i have seen other answer on stackoverflow but none of them are easy to understand, if someone can answer in a easy way please

For your specific use case, I think best is to use a state management solution like Provider, BLoC, or GetX. Docs here:
https://flutter.dev/docs/development/data-and-backend/state-mgmt/options
If you want something quick and easy, you can pass the value you're listening to and a function containing setState to your new page. Normally you'd do this with a child widget rather than new page, so it might get a bit complicated -- you'll need to rebuild the entire page after the setState. Easiest way I can think of doing that is with Navigator.pushReplacement.
Some code (I wrote this in stackoverflow not my IDE so probably has errors):
class AppSettings extends StatefulWidget {
final Function callback;
final bool isVibrationEnabled;
AppSettings({
#required this.callback,
#required this.isVibrationEnabled,
});
}
...
In your AppSettingsState use:
FlatButton(
color: Colors.grey,
child: Text(
widget.isVibrationEnabled ? "Disable Vibration" : "Enable Vibration"),
onPressed: () => widget.callback(),
),
And in your main file, when creating your appsettings use something like:
MaterialButton(
color: Colors.grey,
child: Text("Open app setting"),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AppSettings(
isVibrationEnabled: isVibrationEnabled,
callback: callback,
),
),
);
},
)
void Function callback() {
setState(() => isVibrationEnabled = !isVibrationEnabled);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => AppSettings(
isVibrationEnabled: isVibrationEnabled,
callback: callback,
),
),
);
}
Again, you should probably use a state management solution for this specific use case. Rebuilding a page from another page seems messy. But it should work.
And yes, you're using the callback within your callback. So you may need to put the callback near the top of your file, or outside the main function to make it work right.

Related

Update Cart badge counter in AppBar from multiple widgets

I am using a Reusable AppBar Widget which has title and action buttons.
In app bar actions, there is favorites icon button and cart icon button with a badge showing the total items in cart :
App Bar widget:
import 'package:badges/badges.dart';
import 'package:flutter/material.dart';
class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
final BuildContext context;
final String title;
final bool showBackButton;
final Widget widget;
final bool showActions;
CustomAppBar({
#required this.context,
#required this.title,
this.showBackButton = true,
this.widget,
this.showActions = true,
});
#override
Widget build(BuildContext context) {
return AppBar(
title: Text(title),
leading: showBackButton
? new IconButton(
icon: new Icon(
Icons.arrow_back,
),
onPressed: () {
if (widget != null) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => widget,
),
);
return true;
} else {
Navigator.pop(context);
}
},
)
: null,
actions: !showActions ? null : <Widget>[
IconButton(
icon: const Icon(Icons.favorite_border),
onPressed: () {
Navigator.pushReplacement(
context,
MaterialPageRoute<void>(
builder: (BuildContext context) {
return MainHome(
selectedIndex: 1,
);
},
),
);
},
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 5),
child: Badge(
position: BadgePosition.topEnd(top: 3, end: 3),
animationDuration: Duration(milliseconds: 300),
animationType: BadgeAnimationType.slide,
badgeColor: Colors.white,
toAnimate: true,
badgeContent: Text(
'5',
style: TextStyle(
fontSize: 8,
color: Theme.of(context).primaryColor,
fontWeight: FontWeight.bold),
),
child: IconButton(
icon: const Icon(Icons.shopping_cart_rounded),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute<void>(
builder: (BuildContext context) {
return MainHome(
selectedIndex: 2,
);
},
),
);
},
),
),
),
],
);
}
#override
Size get preferredSize {
return new Size.fromHeight(kToolbarHeight);
}
}
The app bar is used in all screens but with different parameters.
I want to update the counter in app bar whenever user add to cart, update cart, remove from cart. This can happens from multiple pages:
Products list page, product details page, shopping cart page, search suggestions widget. All of these pages have cart actions(add/update/delete).
I don't want to manage this counter in each page. I need a way to manage it in one place and be notified whenever cart updated in order to update badge
I searched a lot. Some uses GLobalKey to manage state but doesn't work in my case as the appbar widget is stateless cannot be stateful.
i suggest that you use any kind of providers like the provider package or riverpod , you need something called notifyListeners , to notify the cart badge every time you add a new item to your cart , otherwise you cart item in your case won't be updated except on widget rebuild ; like navigating and such .
you can check riverpod and provider from pub.dev , make sure to fully understand the docs because it can be tricky !
Hope this answer helps you !
what you want to do is to manage the state of the app. The way you are doing is pretty hard.
Maybe you should look at some state managment solution .
here is a link for a good intro and it talk about what you trying to acheive
intro to state managment
Here is a list of some popular state managment:
Provider (easy to use and recommanded by the Flutter Community)
Bloc (good for very large projects)
GetX (Easy and good to use)
Riverpod (It's provider but more powerful)
There is no a perfect of choice just use what you found good for need.
As suggested by Fahmi Sawalha and Boris Kamtou, I used the provider package to solve the problem.
I will share the code in case anyone needs it. The code helps you create custom AppBar as stateless widget with parameters like title, context, showBackButton, showActions.
And also the code helps you use the provider package to manage state and update ui from different screens.
First Create CartCounterNotifier class at any accessible place in your app. It should extend ChangeNotifier in order to notify listeners when value changed so that ui consuming this provider rebuilds:
CartManager is a Mixin where I manage cart data in DB.
getCartItemsCount() function gets the summation of items' quantities in cart.
cart_counter.dart
import 'package:flutter/material.dart';
class CartCounterNotifier extends ChangeNotifier with CartManager {
int _value = 0;
int get value => _value;
CartCounterNotifier() {
getCartItemsCount().then((counter) {
_value = counter ?? 0;
notifyListeners();
});
}
// You can send send parameters to update method. No need in my case.
// Example: void update(int newvalue) async { ...
void update() async {
int counter = await getCartItemsCount();
_value = counter ?? 0;
notifyListeners();
}
}
The class has two methods:
Constructor to initialize the counter and update() method to be called when value changed.
I have created custom appbar to use across all pages. It is a stateless widget which implements PreferedSizeWidget.
Wrap the widget where you want to show the counter with Consumer. It is better to go deep as possible. In my case, instead of wrapping the whole AppBar with consumer, I just wrapped the text widget showing the counter
custom_app_bar.dart
import 'package:badges/badges.dart'; // add badges to pubspec in order to add badge for icons
import 'package:flutter/material.dart';
import 'package:order_flutter_package/services/cart_counter.dart';
import 'package:order_flutter_package/ui/views/home_view/home_view.dart';
import 'package:provider/provider.dart';
class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
final BuildContext context;
final String title;
final bool showBackButton;
final Widget widget;
final bool showActions;
CustomAppBar({
#required this.context,
#required this.title,
this.showBackButton = true,
this.widget,
this.showActions = true,
});
#override
Widget build(BuildContext context) {
return PreferredSize(
preferredSize: Size.fromHeight(50),
child: AppBar(
title: Text(title),
leading: showBackButton
? new IconButton(
icon: new Icon(
Icons.arrow_back,
),
onPressed: () {
if (widget != null) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => widget,
),
);
return true;
} else {
Navigator.pop(context);
}
},
)
: null,
actions: !showActions
? null
: <Widget>[
IconButton(
icon: const Icon(Icons.favorite_border),
onPressed: () {
Navigator.pushReplacement(
context,
MaterialPageRoute<void>(
builder: (BuildContext context) {
return MainHome(
selectedIndex: 1,
);
},
),
);
},
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 5),
child: Badge(
position: BadgePosition.topEnd(top: 3, end: 3),
animationDuration: Duration(milliseconds: 300),
animationType: BadgeAnimationType.slide,
badgeColor: Colors.white,
toAnimate: true,
badgeContent: Consumer<CartCounterNotifier>(
builder: (context, cartCounter, child) {
return Text(
cartCounter.value.toString(),
style: TextStyle(
fontSize: 8,
color: Theme.of(context).primaryColor,
fontWeight: FontWeight.bold),
);
}),
child: IconButton(
icon: const Icon(Icons.shopping_cart_rounded),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute<void>(
builder: (BuildContext context) {
return MainHome(
selectedIndex: 2,
);
},
),
);
},
),
),
),
],
),
);
}
#override
Size get preferredSize {
return new Size.fromHeight(kToolbarHeight);
}
}
Then wrap your main.dart app with ChangeNotifierProvider
import 'package:provider/provider.dart';
import 'package:order_flutter_package/services/cart_counter.dart';
ChangeNotifierProvider(
create: (context) => CartCounterNotifier(),
child: MyApp(),
)
Finally, in order to update counter, in any screen,
// Very important to import provider else you got an error
import 'package:provider/provider.dart';
// On button pressed or any event
var cartCounter = context.read<CartCounterNotifier>();
cartCounter.update();

Set barrier dismissible after dialog shown

This is the way I normally set the dialog's barrierDismissible field to true or false
showDialog(
barrierDismissible: false,
builder: ...
)
However, it implies that dialog is ALWAYS true or false.
Is there any way to start a dialog barrierDismissible as false and change it to true after one second?
It looks like flutter declarative approach wasn't applied to this widget. Therefore you should do everything yourself.
First handle yourself the tap with:
A general gesture detector which will be used to dismiss the dialog.
A gesture detector around your dialog in order to prevent the tap event to bubble up if it happened in the widget inside your dialog.
Second use a variable to state if the barrierDismissible should be activated or not, and modify this variable after 1 second. This is the variable which should be used be the general gesture detector in order to know if it should dismiss the dialog or not.
Here is a quick exemple, just tap the FAB:
import 'package:flutter/material.dart';
void main() => runApp(
MaterialApp(
home: MyApp(),
),
);
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool barrierDismissible = false;
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
barrierDismissible = false;
Future.delayed(
Duration(seconds: 1),
() => setState(() {
barrierDismissible = true;
}));
return GestureDetector(
onTap: () {
if (barrierDismissible) {
Navigator.of(context, rootNavigator: true).pop();
}
},
child: Material(
color: Colors.transparent,
child: GestureDetector(
onTap: () {},
child: Center(
child: Container(
height: 200,
width: 200,
color: Colors.red,
),
),
),
),
);
},
);
},
),
);
}
}
create bool variable and set that to the barrierDismissible property and use Future.delayed(duration:Duration(seconds:1)) to mak one second count then when the counter completes set the variable to true like this.
onPressed:(){
bool dismissible=false;
showDialog(context: context,barrierDismissible: dismissible); //add your child
Future.delayed(Duration(seconds: 1)).whenComplete(() {
setState(() {
dismissible=true;
});
});
}

How to dismiss a Dialog alert widget automatically in Flutter?

Forgive me if its a noob question or the code seems too basic, I am new to flutter.
I have done my fair share of googling but i havent been able to find a solution.
As soon as the future function starts, i want to show the Loading alert. When the API request/response is processed, if an error has occurred it should be shown in showErrorDialog, and the LoadingDialog should be dismissed automatically.
Right now, ErrorDialog shows and can be dismissed with its button, but then LoadingDialog does not get dismissed.
I can do it with Future.delayed but that is simply a work around has has too many variable outcomes. Here is dummy code:
import 'package:flutter/material.dart';
class RandomScreen extends StatefulWidget {
#override
_RandomScreenState createState() => _RandomScreenState();
}
class _RandomScreenState extends State<RandomScreen> {
Future<void> _submitApiRequest() async {
try {
_showLoadingAlert();
//processing the API request/response here.
} catch (error) {
_showErrorDialogue(error.toString());
}
}
void _showErrorDialogue(String errorMessage) {
showDialog(
context: context,
builder: (ctx) => Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0),
),
child: Column(
children: <Widget>[
Text(errorMessage),
FlatButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(
'Dismiss',
),
),
],
),
),
);
}
void _showLoadingAlert() {
showDialog(
context: context,
builder: (ctx) => CircularProgressIndicator(),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Random Screen'),
),
body: Center(
child: RaisedButton(
onPressed: _submitApiRequest,
child: Text('Submit'),
),
),
);
}
}
The Way you are trying to do might not be the best way in case you are trying to call API and show a loading screen . I would recommend you to use ModalProgressHud
This will show a loader while you are trying to do API call and once API response received you can hide the loader using a state variable.
In case you still want to do using alert box ,
you need to call Navigator.pop as mentioned in documentation.
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).

AlertDialog function runs on everypage in flutter

theAlertDialog in this code should run only when the user access the Account page (obviously), but while testing it, it runs on the Account page and all the next pages, and it is even duplicated, i mean when i head from Account page to another page the AlertDialog will be displayed twice
class Account extends StatefulWidget {
#override
_AccountState createState() => _AccountState();
}
class _AccountState extends State<Account> {
#override
Widget build(BuildContext context) {
Future.delayed(Duration.zero, () => FirstRun(context));
return Scaffold(
//there are alot of widgets here like drawer but all of it works fine
//i don't think its necessary to write it
);
}
FirstRun(BuildContext context) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
bool first = (prefs.getBool('firstUse'));
print('Pressed $first');
if (first == null) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
backgroundColor: Color(0xaa6b6b6b),
elevation: 10,
content: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
'first run dialog',
overflow: TextOverflow.ellipsis,
maxLines: 6,
style: TextStyle(
color: Colors.white,
fontSize: 24,
),
textAlign: TextAlign.center,
),
Container(
child: MaterialButton(
onPressed: () {
prefs.setBool('firstUse', false);
Navigator.of(context).pop();
print('Pressed $first');
},
child: Text(
'ok',
),
))
],
),
);
},
);
}
}
}
maybe it happens because you start showing the alert on build method. try to show it on initState method of the Account widget.
class _AccountState extends State<Account> {
#override
initState() {
Future.delayed(Duration.zero, () => FirstRun(this.context));
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
//there are alot of widgets here like drawer but all of it works fine
//i don't think its necessary to write it
);
}
as a work around i call the next page using Navigator.Replace instead of Navigator.push , but i dont think this is a real solution

Force Flutter navigator to reload state when popping

I have one StatefulWidget in Flutter with button, which navigates me to another StatefulWidget using Navigator.push(). On second widget I'm changing global state (some user preferences). When I get back from second widget to first, using Navigator.pop() the first widget is in old state, but I want to force it's reload. Any idea how to do this? I have one idea but it looks ugly:
pop to remove second widget (current one)
pop again to remove first widget (previous one)
push first widget (it should force redraw)
There's a couple of things you could do here. #Mahi's answer while correct could be a little more succinct and actually use push rather than showDialog as the OP was asking about. This is an example that uses Navigator.push:
import 'package:flutter/material.dart';
class SecondPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
color: Colors.green,
child: Column(
children: <Widget>[
RaisedButton(
onPressed: () => Navigator.pop(context),
child: Text('back'),
),
],
),
);
}
}
class FirstPage extends StatefulWidget {
#override
State<StatefulWidget> createState() => new FirstPageState();
}
class FirstPageState extends State<FirstPage> {
Color color = Colors.white;
#override
Widget build(BuildContext context) {
return new Container(
color: color,
child: Column(
children: <Widget>[
RaisedButton(
child: Text("next"),
onPressed: () async {
final value = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondPage()),
),
);
setState(() {
color = color == Colors.white ? Colors.grey : Colors.white;
});
},
),
],
),
);
}
}
void main() => runApp(
MaterialApp(
builder: (context, child) => SafeArea(child: child),
home: FirstPage(),
),
);
However, there's another way to do this that might fit your use-case well. If you're using the global as something that affects the build of your first page, you could use an InheritedWidget to define your global user preferences, and each time they are changed your FirstPage will rebuild. This even works within a stateless widget as shown below (but should work in a stateful widget as well).
An example of inheritedWidget in flutter is the app's Theme, although they define it within a widget instead of having it directly building as I have here.
import 'package:flutter/material.dart';
import 'package:meta/meta.dart';
class SecondPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
color: Colors.green,
child: Column(
children: <Widget>[
RaisedButton(
onPressed: () {
ColorDefinition.of(context).toggleColor();
Navigator.pop(context);
},
child: new Text("back"),
),
],
),
);
}
}
class ColorDefinition extends InheritedWidget {
ColorDefinition({
Key key,
#required Widget child,
}): super(key: key, child: child);
Color color = Colors.white;
static ColorDefinition of(BuildContext context) {
return context.inheritFromWidgetOfExactType(ColorDefinition);
}
void toggleColor() {
color = color == Colors.white ? Colors.grey : Colors.white;
print("color set to $color");
}
#override
bool updateShouldNotify(ColorDefinition oldWidget) =>
color != oldWidget.color;
}
class FirstPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
var color = ColorDefinition.of(context).color;
return new Container(
color: color,
child: new Column(
children: <Widget>[
new RaisedButton(
child: new Text("next"),
onPressed: () {
Navigator.push(
context,
new MaterialPageRoute(builder: (context) => new SecondPage()),
);
}),
],
),
);
}
}
void main() => runApp(
new MaterialApp(
builder: (context, child) => new SafeArea(
child: new ColorDefinition(child: child),
),
home: new FirstPage(),
),
);
If you use inherited widget you don't have to worry about watching for the pop of the page you pushed, which will work for basic use-cases but may end up having problems in a more complex scenario.
Short answer:
Use this in 1st page:
Navigator.pushNamed(context, '/page2').then((_) => setState(() {}));
and this in 2nd page:
Navigator.pop(context);
There are 2 things, passing data from
1st Page to 2nd
Use this in 1st page
// sending "Foo" from 1st
Navigator.push(context, MaterialPageRoute(builder: (_) => Page2("Foo")));
Use this in 2nd page.
class Page2 extends StatelessWidget {
final String string;
Page2(this.string); // receiving "Foo" in 2nd
...
}
2nd Page to 1st
Use this in 2nd page
// sending "Bar" from 2nd
Navigator.pop(context, "Bar");
Use this in 1st page, it is the same which was used earlier but with little modification.
// receiving "Bar" in 1st
String received = await Navigator.push(context, MaterialPageRoute(builder: (_) => Page2("Foo")));
For me this seems to work:
Navigator.of(context).pushNamed("/myRoute").then((value) => setState(() {}));
Then simply call Navigator.pop() in the child.
The Easy Trick is to use the Navigator.pushReplacement method
Page 1
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => Page2(),
),
);
Page 2
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => Page1(),
),
);
Simply add .then((value) { setState(() {}); after Navigator.push on page1() just like below:
Navigator.push(context,MaterialPageRoute(builder: (context) => Page2())).then((value) { setState(() {});
Now when you use Navigator.pop(context) from page2 your page1 rebuild itself
You can use pushReplacement and specify the new Route
onTapFunction(BuildContext context) async {
final reLoadPage = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => IdDetailsScreen()),
);
if (reLoadPage) {
setState(() {});
}
}
Now while doing Navigator.pop from second page to come back to first page just return some value which in my case is of bool type
onTap: () {
Navigator.pop(context, true);
}
my solution went by adding a function parameter on SecondPage, then received the reloading function which is being done from FirstPage, then executed the function before the Navigator.pop(context) line.
FirstPage
refresh() {
setState(() {
//all the reload processes
});
}
then on pushing to the next page...
Navigator.push(context, new MaterialPageRoute(builder: (context) => new SecondPage(refresh)),);
SecondPage
final Function refresh;
SecondPage(this.refresh); //constructor
then on before the navigator pop line,
widget.refresh(); // just refresh() if its statelesswidget
Navigator.pop(context);
Everything that needs to be reloaded from the previous page should be updated after the pop.
This work really good, i got from this doc from flutter page: flutter doc
I defined the method to control navigation from first page.
_navigateAndDisplaySelection(BuildContext context) async {
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => AddDirectionPage()),
);
//below you can get your result and update the view with setState
//changing the value if you want, i just wanted know if i have to
//update, and if is true, reload state
if (result) {
setState(() {});
}
}
So, i call it in a action method from a inkwell, but can be called also from a button:
onTap: () {
_navigateAndDisplaySelection(context);
},
And finally in the second page, to return something (i returned a bool, you can return whatever you want):
onTap: () {
Navigator.pop(context, true);
}
Put this where you're pushing to second screen (inside an async function)
Function f;
f= await Navigator.pushNamed(context, 'ScreenName');
f();
Put this where you are popping
Navigator.pop(context, () {
setState(() {});
});
The setState is called inside the pop closure to update the data.
I had a similar issue.
Please try this out:
In the First Page:
Navigator.push( context, MaterialPageRoute( builder: (context) => SecondPage()), ).then((value) => setState(() {}));
After you pop back from SecondPage() to FirstPage() the "then" statement will run and refresh the page.
You can pass back a dynamic result when you are popping the context and then call the setState((){}) when the value is true otherwise just leave the state as it is.
I have pasted some code snippets for your reference.
handleClear() async {
try {
var delete = await deleteLoanWarning(
context,
'Clear Notifications?',
'Are you sure you want to clear notifications. This action cannot be undone',
);
if (delete.toString() == 'true') {
//call setState here to rebuild your state.
}
} catch (error) {
print('error clearing notifications' + error.toString());
}
}
Future<bool> deleteLoanWarning(BuildContext context, String title, String msg) async {
return await showDialog<bool>(
context: context,
child: new AlertDialog(
title: new Text(
title,
style: new TextStyle(fontWeight: fontWeight, color: CustomColors.continueButton),
textAlign: TextAlign.center,
),
content: new Text(
msg,
textAlign: TextAlign.justify,
),
actions: <Widget>[
new Container(
decoration: boxDecoration(),
child: new MaterialButton(
child: new Text('NO',),
onPressed: () {
Navigator.of(context).pop(false);
},
),
),
new Container(
decoration: boxDecoration(),
child: new MaterialButton(
child: new Text('YES', ),
onPressed: () {
Navigator.of(context).pop(true);
},
),
),
],
),
) ??
false;
}
Regards,
Mahi
In flutter 2.5.2 this is worked for me also it works for updating a list
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondPage()))
.then((value) => setState(() {}));
then in the second page I just code this
Navigator.pop(context);
I have a ListView in fist page which is display a list[] data, the second page was updating the data for my list[] so the above code works for me.
Needed to force rebuild of one of my stateless widgets. Did't want to use stateful. Came up with this solution:
await Navigator.of(context).pushNamed(...);
ModalRoute.of(enclosingWidgetContext);
Note that context and enclosingWidgetContext could be the same or different contexts. If, for example, you push from inside StreamBuilder, they would be different.
We don't do anything here with ModalRoute. The act of subscribing alone is enough to force rebuild.
If you are using an alert dialog then you can use a Future that completes when the dialog is dismissed. After the completion of the future you can force widget to reload the state.
First page
onPressed: () async {
await showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
....
);
}
);
setState(() {});
}
In Alert dialog
Navigator.of(context).pop();
This simple code worked for me to go to the root and reload the state:
...
onPressed: () {
Navigator.of(context).pushNamedAndRemoveUntil('/', ModalRoute.withName('/'));
},
...
In short, you should make the widget watch the state. You need state management for this.
My method is based on Provider explained in Flutter Architecture Samples as well as Flutter Docs. Please refer to them for more concise explanation but more or less the steps are :
Define your state model with states that the widget needs to observe.
You could have multiple states say data and isLoading, to wait for some API process. The model itself extends ChangeNotifier.
Wrap the widgets that depend on those states with watcher class.
This could be Consumer or Selector.
When you need to "reload", you basically update those states and broadcast the changes.
For state model the class would look more or less as follows. Pay attention to notifyListeners which broadcasts the changes.
class DataState extends ChangeNotifier{
bool isLoading;
Data data;
Future loadData(){
isLoading = true;
notifyListeners();
service.get().then((newData){
isLoading = false;
data = newData;
notifyListeners();
});
}
}
Now for the widget. This is going to be very much a skeleton code.
return ChangeNotifierProvider(
create: (_) => DataState()..loadData(),
child: ...{
Selector<DataState, bool>(
selector: (context, model) => model.isLoading,
builder: (context, isLoading, _) {
if (isLoading) {
return ProgressBar;
}
return Container(
child: Consumer<DataState>(builder: (context, dataState, child) {
return WidgetData(...);
}
));
},
),
}
);
Instance of the state model is provided by ChangeNotifierProvider. Selector and Consumer watch the states, each for isLoading and data respectively. There is not much difference between them but personally how you use them would depend on what their builders provide. Consumer provides access to the state model so calling loadData is simpler for any widgets directly underneath it.
If not then you can use Provider.of. If we'd like to refresh the page upon return from the second screen then we can do something like this:
await Navigator.push(context,
MaterialPageRoute(
builder: (_) {
return Screen2();
));
Provider.of<DataState>(context, listen: false).loadData();
For me worked:
...
onPressed: (){pushUpdate('/somePageName');}
...
pushUpdate (string pageName) async { //in the same class
await pushPage(context, pageName);
setState(() {});
}
//---------------------------------------------
//general sub
pushPage (context, namePage) async {
await Navigator.pushNamed(context, namePage);
}
In this case doesn't matter how you pop (with button in UI or "back" in android) the update will be done.
Very simply use "then" after you push, when navigator pops back it will fire setState and the view will refresh.
Navigator.push(blabla...).then((value) => setState(() {}))
// Push to second screen
await Navigator.push(
context,
CupertinoPageRoute(
builder: (context) => SecondScreen(),
),
);
// Call build method to update any changes
setState(() {});
Use setstate in your navigation push code.
Navigator.push(context, MaterialPageRoute(builder: (context) => YourPage())).then((value) {
setState(() {
// refresh state
});
});
This simple code goes to the root and reloads the state even without setState:
Navigator.pushAndRemoveUntil(context, MaterialPageRoute(builder: (context) => MainPage()), (Route<dynamic> route) => false,); //// this MainPage is your page to refresh