Related
I'm having an issue with my widget running its FutureBuilder code multiple times with an already resolved Future. Unlike the other questions on SO about this, my build() method isn't being called multiple times.
My future is being called outside of build() in initState() - it's also wrapped in an AsyncMemoizer.
Relevant code:
class _HomeScreenState extends State<HomeScreen> {
late final Future myFuture;
final AsyncMemoizer _memoizer = AsyncMemoizer();
#override
void initState() {
super.initState();
/// provider package
final homeService = context.read<HomeService>();
myFuture = _memoizer.runOnce(homeService.getMyData);
}
#override
Widget build(BuildContext context) {
print("[HOME] BUILDING OUR HOME SCREEN");
return FutureBuilder(
future: myFuture,
builder: ((context, snapshot) {
print("[HOME] BUILDER CALLED WITH SNAPSHOT: $snapshot - connection state: ${snapshot.connectionState}");
When I run the code, and trigger the bug (a soft keyboard being shown manages to trigger it 50% of the time, but not all the time), my logs are:
I/flutter (29283): [HOME] BUILDING OUR HOME SCREEN
I/flutter (29283): [HOME] BUILDER CALLED WITH SNAPSHOT: AsyncSnapshot<dynamic>(ConnectionState.waiting, null, null, null) - connection state: ConnectionState.waiting
I/flutter (29283): [HOME] BUILDER CALLED WITH SNAPSHOT: AsyncSnapshot<dynamic>(ConnectionState.done, Instance of 'HomeData', null, null) - connection state: ConnectionState.done
...
/// bug triggered
...
I/flutter (29283): [HOME] BUILDER CALLED WITH SNAPSHOT: AsyncSnapshot<dynamic>(ConnectionState.done, Instance of 'HomeData', null, null) - connection state: ConnectionState.done
The initial call with ConnectionState.waiting is normal, then we get the first build with ConnectionState.done.
After the bug is triggered, I end up with another FutureBuilder resolve without the build() method being called.
Am I missing something here?
Edit with full example
This shows the bug in question - if you click in and out of the TextField, the FutureBuilder is called again.
It seems related to how the keyboard is hidden. If I use the FocusScopeNode method, it will rebuild, whereas if I use FocusManager, it won't, so I'm not sure if this is a bug or not.
import 'package:flutter/material.dart';
void main() async {
runApp(const TestApp());
}
class TestApp extends StatelessWidget {
const TestApp({super.key});
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Testapp',
home: Scaffold(
body: TestAppHomeScreen(),
),
);
}
}
class TestAppHomeScreen extends StatefulWidget {
const TestAppHomeScreen({super.key});
#override
State<TestAppHomeScreen> createState() => _TestAppHomeScreenState();
}
class _TestAppHomeScreenState extends State<TestAppHomeScreen> {
late final Future myFuture;
#override
void initState() {
super.initState();
myFuture = Future.delayed(const Duration(milliseconds: 500), () => true);
print("[HOME] HOME SCREEN INIT STATE CALLED: $hashCode");
}
#override
Widget build(BuildContext context) {
print("[HOME] HOME SCREEN BUILD CALLED: $hashCode");
return FutureBuilder(
future: myFuture,
builder: (context, snapshot) {
print("[HOME] HOME SCREEN FUTURE BUILDER CALLED WITH STATE ${snapshot.connectionState}: $hashCode");
if (snapshot.connectionState == ConnectionState.waiting) {
return const Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
return GestureDetector(
onTapUp: (details) {
// hide the keyboard if it's showing
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.unfocus();
}
// FocusManager.instance.primaryFocus?.unfocus();
},
child: const Scaffold(
body: Center(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 32.0),
child: TextField(),
),
),
),
);
},
);
}
}
Thank you for the full, reproducible example.
print statements inside the builder method of your FutureBuilder are likely misleading you towards the incorrect "culprit".
The key "problem" arises from this line:
FocusScopeNode currentFocus = FocusScope.of(context);
In case you didn't know, Flutter's .of static methods expose InheritedWidget APIs of some kind. By convention, in a .of method you can usually find a call to dependOnInheritedWidgetOfExactType, which is meant to register the caller, i.e. the children Widget, as a dependency, i.e. a Widget that depends and react to changes of a InheritedWidget of that type.
Shortly, putting a .of inside a build method is meant to trigger rebuilds on your Widget: it's actively registered for listening to changes!
In your code, FutureBuilder's builder method is being registered as dependant of FocusScope.of and will be rebuilt if FocusScope changes. And yes, that does happen whenever we change focus. Indeed, you can even move up those few lines (outside GestureDetector, directly in the builder scope), and you'd obtain even more rebuilds (4: one for the first focus change, then others subsequent caused by the focus shift caused by such rebuilds).
One quick fix would be to directly look for the associated InheritedWidget these API expose, and then, instead of a simple .of, you'd call:
context.getElementForInheritedWidgetOfExactType<T>();
EDIT. I just looked for T in your use case. Unluckily, it turns out it is a _FocusMarker extends InheritedWidget class, which is a private class, and therefore it cannot be used outside of its file / package. I'm not sure why they designed the API like that, but I am not familiar with FocusNodes.
An alternative approach would be to simply isolate the children for your FutureBuilder, like so:
builder: (context, snapshot) {
print("[HOME] HOME SCREEN FUTURE BUILDER CALLED WITH STATE ${snapshot.connectionState}: $hashCode");
// ...
return Something();
}
Where Something is just the refactored StatelessWidget that contains the UI you've shown there. This would rebuild just Something and not the whole builder method, if that's your concern.
You want to deepen the "how" and the "whys" of InheritedWidgets, make sure you first watch this video to correctly understand what InheritedWidgets are. Then, if you wish to understand how to exploit didChangeDependencies, watch this other video and you'll be good to go.
You need to understand the role of BuildContext.
Example-1:
I'm using context passed to the Widget.build() method, and doing
FocusScope.of(context).unfocus();
will invoke both build() and builder() method because you're telling Flutter to take the focus away from any widget within the context and therefore the Widget.build() gets called, which further calls the Builder.builder() method.
// Example-1
#override
Widget build(BuildContext context) {
print("Widget.build()");
return Builder(builder: (context2) {
print('Builder.builder()');
return GestureDetector(
onTap: () => FocusScope.of(context).unfocus(), // <-- Using `context`
child: Scaffold(
body: Center(
child: TextField(),
),
),
);
});
}
Example-2:
I'm using context2 passed to the Builder.builder() method, and doing
FocusScope.of(context2).unfocus();
will invoke only the builder() method because you're telling Flutter to take the focus away from any widget within the context2 and thus the Builder.builder() gets called.
// Example-2
#override
Widget build(BuildContext context) {
print("Widget.build()");
return Builder(builder: (context2) {
print('Builder.builder()');
return GestureDetector(
onTap: () => FocusScope.of(context2).unfocus(), // <-- Using `context2`
child: Scaffold(
body: Center(
child: TextField(),
),
),
);
});
}
To answer your question, if you replace
builder: (context, snapshot) { ...}
with
builder: (_, snapshot) { }
then your build() will also get called.
The difference was happen because the context you use is parent
context (from future builder method).
Just wrap GestureDetector with Builder then the result is same as 2nd way.
return Builder(builder: (_context) {
return GestureDetector(
onTapUp: () {
// hide the keyboard if it's showing
final currentFocus = FocusScope.of(_context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.unfocus();
},
} ...
When attempting to dismiss keyboard we should use second way FocusManager.instance.primaryFocus?.unfocus(); as discussion in official issue here:
https://github.com/flutter/flutter/issues/20227#issuecomment-512860882
https://github.com/flutter/flutter/issues/19552
Please try this solution /// provider package up super.initState();
your code will be like this
#override
void initState() {
/// provider package
final homeService = context.read<HomeService>();
myFuture = _memoizer.runOnce(homeService.getMyData);
super.initState();
}
please after trying it tell me the result
pass descendant context to FocusScope.of will not trigger the build(), i think because focus manager remove child for this parent (FutureBuilder), and reassign it based on current context, in this case build() context, so futurebuilder need to rebuild.
Widget build(BuildContext context) {
print("[HOME] HOME SCREEN BUILD CALLED: $hashCode");
return FutureBuilder(
future: myFuture,
builder: (context, snapshot) {
print("[HOME] HOME SCREEN FUTURE BUILDER CALLED WITH STATE ${snapshot.connectionState}: $hashCode");
if (snapshot.connectionState == ConnectionState.waiting) {
return const Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
//make StatefulBuilder as parent will prevent it
return StatefulBuilder(
builder: (context, setState) {
return GestureDetector(
onTapUp: (details) {
// hide the keyboard if it's showing
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.unfocus();
}
// FocusManager.instance.primaryFocus?.unfocus();
},
child: const Scaffold(
body: Center(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 32.0),
child: TextField(),
),
),
),
);
}
);
},
);
}
to prove it , i try to warp it parent (FutureBuilder) with another builder :
return LayoutBuilder(
builder: (context, box) {
print('Rebuild');
return FutureBuilder(
future: myFuture,
builder: (context, snapshot) {
print("[HOME] HOME SCREEN FUTURE BUILDER CALLED WITH STATE ${snapshot.connectionState}: $hashCode");
if (snapshot.connectionState == ConnectionState.waiting) {
return const Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
return GestureDetector(
onTapUp: (details) {
// hide the keyboard if it's showing
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.unfocus();
}
// FocusManager.instance.primaryFocus?.unfocus();
},
child: const Scaffold(
body: Center(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 32.0),
child: TextField(),
),
),
),
);
},
);
}
);
build() method not reinvoked because focusScope manager only rebuild context from FutureBuilder (Parent)
I'm getting a list of clients from the Api, in a clients' list screen, when I want to Update a client, I navigate through DetailClient Screen than navigating through Edit client screen,
the update is working perfect ..but I wanted to navigate back through the first screen "clients list" from the alert dialogue to see the update in the clients' list.
the code is shown as bellow:
Future<void> _saveForm() async {
_form.currentState!.save();
if (_client.id!.isNotEmpty) {
await Provider.of<ClientsProvider>(context, listen: false).updateClients(_client.id!, _client);
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Client has been updated!'),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(builder: ((context) => Clients())));
},
child: Text('Okay'),
),
],
));
}else{
I had the following error while trying to navigate:
Error: Could not find the correct Provider above this Clients Widget
This happens because you used a BuildContext that does not include... the provider
thank you in Advance
You need to wrap your top widget with MultiProvider to be able to locate your provider across the context.
void main(){
runApp(
MultiProvider(
providers: [
Provider<ClientsProvider>(
create: (_) ClientsProvider(),
)
],
child: YourFirstWidget(),
)
);
}
I implemented the alert dialog in the initstate() method but Init state is only called once. In my case I want the alert to appear automatically every time a variable value changes for exemple. ( I need it to suddenly pop up during using the app)
You could use a ValueNotifier and a ValueListenableBuilder so that every time the value in the ValueNotifier changes, the ValueListenableBuilder rebuilds and shows a dialog, like so:
class MyWidget extends StatelessWidget {
ValueNotifier<int> dialogTrigger = ValueNotifier(0);
#override
Widget build(BuildContext context) {
return Column(
children: [
TextButton(
onPressed: () {
var random = Random();
dialogTrigger.value = random.nextInt(100);
},
child: const Text('Click me and change a value')
),
Expanded(
child: ValueListenableBuilder(
valueListenable: dialogTrigger,
builder: (ctx, value, child) {
Future.delayed(const Duration(seconds: 0), () {
showDialog(
context: ctx,
builder: (ctx) {
return AlertDialog(
title: const Text('Dialog'),
content: Text('Hey! I got a $value'),
);
});
});
return const SizedBox();
})
)
]
);
}
}
(Again, I'm only adding a button to change the value, not to launch the dialog. That way you can programmatically change the value, which eventually launches the dialog). See if that works for your purposes.
I have a FloatingActionButton inside a widget tree which has a BlocProvider from flutter_bloc. Something like this:
BlocProvider(
builder: (context) {
SomeBloc someBloc = SomeBloc();
someBloc.dispatch(SomeEvent());
return someBloc;
},
child: Scaffold(
body: ...
floatingActionButton: FloatingActionButton(
onPressed: _openFilterSchedule,
child: Icon(Icons.filter_list),
),
)
);
Which opens a modal bottom sheet:
void _openFilterSchedule() {
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return TheBottomSheet();
},
);
}
I am trying to access SomeBloc using BlocProvider.of<SomeBloc>(context) inside TheBottomSheet but I get the following error:
BlocProvider.of() called with a context that does not contain a Bloc of type SomeBloc.
I have tried to use the solution described in https://stackoverflow.com/a/56533611/2457045 but only works for BottomSheet and not ModalBottomSheet.
Note: This is not restricted to BlocProvider or flutter_bloc. Any Provider from the provider package has the same behaviour.
How can I access BlocProvider.of<SomeBloc>(context) inside the showModalBottomSheet?
In case it's not possible to do that, how to adapt https://stackoverflow.com/a/56533611/2457045 solution to Modal Bottom Sheet?
InheritedWidgets, and therefore Providers, are scoped to the widget tree. They cannot be accessed outside of that tree.
The thing is, using showDialog and similar functions, the dialog is located in a different widget tree – which may not have access to the desired provider.
It is therefore necessary to add the desired providers in that new widget tree:
void myShowDialog() {
final myModel = Provider.of<MyModel>(context, listen: false);
showDialog(
context: context,
builder: (_) {
return Provider.value(value: myModel, child: SomeDialog());
},
);
}
Provider in showModalBottomSheet (Bottom-Sheet)
void myBottomSheet() {
final myModel = Provider.of<MyModel>(context, listen: false);
showModalBottomShee(
context: context,
builder: (_) {
return ListenableProvider.value(
value: myModel,
child: Text(myModel.txtValue),
);
},
);
}
You need move Provider to top layer(MaterialApp)
According to picture, Dialog widget is under MaterialApp, so this is why you using wrong context
wrap your whole child widget inside the consumer.
void myShowDialog() {
showDialog(
context: context,
builder: Consumer<MyModel>(
builder: (context, value, builder) {
retuen widget();
}
);
}
You should split Scaffold widget and its children, to another StatefulWidget
From single Widget
class MainScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocProvider(
builder: (context) {
SomeBloc someBloc = SomeBloc();
someBloc.dispatch(SomeEvent());
return someBloc;
},
child: Scaffold(
body: ...
floatingActionButton: FloatingActionButton(
onPressed: _openFilterSchedule,
child: Icon(Icons.filter_list),
),
)
);
}
}
Splitted into these two widget
class MainScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocProvider(
builder: (context) {
SomeBloc someBloc = SomeBloc();
someBloc.dispatch(SomeEvent());
return someBloc;
},
child: Screen(),
);
}
}
and ..
class Screen extends StatelessWidget {
void _openFilterSchedule() {
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return TheBottomSheet();
},
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: ...
floatingActionButton: FloatingActionButton(
onPressed: _openFilterSchedule,
child: Icon(Icons.filter_list),
),
);
}
}
I found a solution, Just return your showModalBottomSheet with a StatefulBuilder and use the context of your modalsheet builder to pass to your provider. a snippet of my code below:
Future<Widget> showModal(int qty, Product product) async {
return await showModalBottomSheet(
isScrollControlled: true,
backgroundColor: Colors.transparent,
context: context,
builder: (BuildContext ctx) {
return StatefulBuilder(builder: (ctx, state) {
return Container(
child: RaisedButton(
onPressed: () {
Product prod = Product(product.id,
product.sku, product.name, qty);
Provider.of<CartProvider>(ctx, listen:
false).addCart(prod);}),);
}
}
);
}
TLDR: Make sure your import statement's casings match your project's folder casings.
I came across one other quirk while debugging this same error. I had several providers that were all working, including in showModalBottomSheets, however one was not working. After combing through the entire widget tree, without finding any discrepancies, I found that I had capitalized the first letter of a folder on one of the import statements of my problem-child notifier. I think this confused the compiler and caused it to throw the Could not find the correct Provider above this widget error.
After ensuring the import statement casing matched the folder name, my provider problems were resolved. Hopefully this will save someone a headache.
Not finding a clear explanation of adding multiple provided values, I thought I'd share here for reference.
await showMobileModals(
isDismissible: false,
context: context,
child: MultiProvider(
providers: [
Provider.value(
value: provided_one,
),
Provider.value(
value: provided_two,
),
Provider.value(
value: provided_three,
),
],
child: Container(),
),
);
Faced the same issue while dealing with showModelBottomSheet, since it happens to work in a different (context)widget tree I had to level up my state to that of the app so that I could access my provider using the context.
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