I'm working on writing a Flutter application where I want to check on start up if a certain value is stored. If this value is not stored (i.e. the app was just downloaded or data was cleared), I want to create a dialog and have the user choose which value to store out of 3 options. I also want users to be able to change this value through the settings pane as well where I feel it would be easier to just make the same alert dialog pop up.
For my app, my main view is a stateful widget and I've added the code to make the dialog display on startup.
class Home extends StatefulWidget {
const Home({Key key}) : super(key: key);
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
bool is_setting_set = false;
#override
void initState() {
super.initState();
showSettingChoosingDialog(context);
}
void showSettingChoosingDialog(BuildContext context) async {
return showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Choose your setting'),
content: CustomRadioListTileWidget(),
actions: <Widget>[
TextButton(
child: const Text('Done'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
));
}
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
drawer: Drawer(
child: ListView(
children: [
ListTile(
title: const Text('Settings'),
onTap: () {},
)
],
),
),
appBar: AppBar(
title: const Text('App'),
),
body: TabBarView(...),
),
);
}
}
but this throws an error because the CustomRadioListTileWidget is also a StatefulWidget. The stack trace is:
VERBOSE-2:ui_dart_state.cc(199)] Unhandled Exception: dependOnInheritedWidgetOfExactType<_LocalizationsScope>() or dependOnInheritedElement() was called before _HomeState.initState() completed.
When an inherited widget changes, for example if the value of Theme.of() changes, its dependent widgets are rebuilt. If the dependent widget's reference to the inherited widget is in a constructor or an initState() method, then the rebuilt dependent widget will not reflect the changes in the inherited widget.
Typically references to inherited widgets should occur in widget build() methods. Alternatively, initialization based on inherited widgets can be placed in the didChangeDependencies method, which is called after initState and whenever the dependencies change thereafter.
#0 StatefulElement.dependOnInheritedElement.<anonymous closure> (package:flutter/src/widgets/framework.dart:4821:9)
#1 StatefulElement.dependOnInheritedElement (package:flutter/src/widgets/framework.dart:4864:6)
#2 Element.<…>
I could move the CustomRadioListTileWidget code to this widget here but then I don't want to copy paste the same code for use in my settings pane. Is there a way to share this alert dialog with multiple components?
IMO it's not related to the CustomRadioListTileWidget widget and the fact that it's a stateful widget.
The thing is that you don't have access to context in the initState, you need to call the showSettingChoosingDialog after the build method has been called once and then you have access to context object.
But you can still do it in initState, just call the showSettingChoosingDialog function in a Future.delayed(Duration.zero, clbk) call back, like the code below:
#override
void initState() {
Future.delayed(Duration.zero, () => showSettingChoosingDialog(context));
super.initState();
}
This ensures that the build has been run once, why? Because the callback inside the Future.delayed is called when the current call stack is empty.
BTW, you don't need to pass the context into the showSettingChoosingDialog function, it has access to the context provided by State class.
So it can become:
Future.delayed(Duration.zero, () => showSettingChoosingDialog());
Or shorter:
Future.delayed(Duration.zero, showSettingChoosingDialog);
Related
Is it correct to add event to Bloc in initState or didChangeDependencies?
I tried to add event in initState and didChangeDependencies. But throw this exception.
But when this page opens again, everything work fine.
The following StateError was thrown building BlocBuilder<SlotsBloc, SlotsState>(dirty, dependencies: [_InheritedProviderScope<SlotsBloc?>], state: _BlocBuilderBaseState<SlotsBloc, SlotsState>#29045):
Bad state: No element
I don't think this is how Bloc is intended to be used.
By reading a lot of examples on the official documentation, i see that the BlocProviders are always placed somewhere within the widget tree.
This means that they are not accessible without a BuildContext, so you can't access them in initState or in didChangeDependencies.
Another problem with this approach is that you are letting the user interface dictate state changes, and it should be the opposite.
you can do sth like this:
BlocProvider<SlotsBloc>(
create: (context) => SlotsBloc()..add(SlotInitState()),
child: child
)
There's no problem calling in initState, it all depends on what you need.
See in the example that I instantiated the bloc inside the statefull, called an event and passed it to BlocBuilder, with this you can use the bloc instance to add other events without having to read the context and at the same time I provide it to BlocBuilder, if you don't pass the bloc to BlocBuilder it will do the same thing you did in initState, run a BlocProvider.of<ExampleBloc>(context) to access the instance in context.
Observer this official example from the documentation, github_Search.
class MyHomeStatefull extends StatefulWidget {
const MyHomeStatefull({Key? key}) : super(key: key);
#override
State<MyHomeStatefull> createState() => _MyHomeStatefullState();
}
class _MyHomeStatefullState extends State<MyHomeStatefull> {
late ExampleBloc exampleBloc;
#override
void initState() {
super.initState();
// instance bloc and insert event
exampleBloc = BlocProvider.of<ExampleBloc>(context)..add(StartEvent());
//or
//exampleBloc = context.read<ExampleBloc>()..add(StartEvent());
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Page')),
body: BlocBuilder<ExampleBloc, ExampleState>(
bloc: exampleBloc,
builder: (context, state) {
if (state.isLoading) {
return const Center(child: CircularProgressIndicator());
}
return ListView(
children: List.generate(
state.listInterger.length,
(index) => ListTile(
title: Text('${state.listInterger[index]}'),
),
),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: (){
exampleBloc.add(OtherEvent());
},
),
);
}
}
In Flutter, all Navigator functions that push a new element onto the navigation stack return a Future as it's possible for the caller to wait for the execution and handle the result.
I make heavy use of it e. g. when redirecting the user (via push()) to a new page. As the user finishes the interaction with that page I sometimes want the original page to also pop():
onTap: () async {
await Navigator.of(context).pushNamed(
RoomAddPage.routeName,
arguments: room,
);
Navigator.of(context).pop();
},
A common example is the usage of a bottom sheet with a button with a sensitive action (like deleting an entity). When a user clicks the button, another bottom sheet is opened that asks for the confirmation. When the user confirms, the confirm dialog is to be dismissed, as well as the first bottom sheet that opened the confirm bottom sheet.
So basically the onTap property of the DELETE button inside the bottom sheet looks like this:
onTap: () async {
bool deleteConfirmed = await showModalBottomSheet<bool>(/* open the confirm dialog */);
if (deleteConfirmed) {
Navigator.of(context).pop();
}
},
Everything is fine with this approach. The only problem I have is that the linter raises a warning: use_build_context_synchronously because I use the same BuildContext after the completion of an async function.
Is it safe for me to ignore / suspend this warning? But how would I wait for a push action on the navigation stack with a follow-up code where I use the same BuildContext? Is there a proper alternative? There has to be a possibility to do that, right?
PS: I can not and I do not want to check for the mounted property as I am not using StatefulWidget.
Short answer:
It's NOT SAFE to always ignore this warning, even in a Stateless Widget.
A workaround in this case is to use the context before the async call. For example, find the Navigator and store it as a variable. This way you are passing the Navigator around, not passing the BuildContext around, like so:
onPressed: () async {
final navigator = Navigator.of(context); // store the Navigator
await showDialog(
context: context,
builder: (_) => AlertDialog(
title: Text('Dialog Title'),
),
);
navigator.pop(); // use the Navigator, not the BuildContext
},
Long answer:
This warning essentially reminds you that, after an async call, the BuildContext might not be valid anymore. There are several reasons for the BuildContext to become invalid, for example, having the original widget destroyed during the waiting, could be one of the (leading) reasons. This is why it's a good idea to check if your stateful widget is still mounted.
However, we cannot check mounted on stateless widgets, but it absolutely does not mean they cannot become unmounted during the wait. If conditions are met, they can become unmounted too! For example, if their parent widget is stateful, and if their parent triggered a rebuild during the wait, and if somehow a stateless widget's parameter is changed, or if its key is different, it will be destroyed and recreated. This will make the old BuildContext invalid, and will result in a crash if you try to use the old context.
To demonstrate the danger, I created a small project. In the TestPage (Stateful Widget), I'm refreshing it every 500 ms, so the build function is called frequently. Then I made 2 buttons, both open a dialog then try to pop the current page (like you described in the question). One of them stores the Navigator before opening the dialog, the other one dangerously uses the BuildContext after the async call (like you described in the question). After clicking a button, if you sit and wait on the alert dialog for a few seconds, then exit it (by clicking anywhere outside the dialog), the safer button works as expected and pops the current page, while the other button does not.
The error it prints out is:
[VERBOSE-2:ui_dart_state.cc(209)] Unhandled Exception: Looking up a
deactivated widget's ancestor is unsafe. At this point the state of
the widget's element tree is no longer stable. To safely refer to a
widget's ancestor in its dispose() method, save a reference to the
ancestor by calling dependOnInheritedWidgetOfExactType() in the
widget's didChangeDependencies() method.
#0 Element._debugCheckStateIsActiveForAncestorLookup. (package:flutter/src/widgets/framework.dart:4032:9)
#1 Element._debugCheckStateIsActiveForAncestorLookup (package:flutter/src/widgets/framework.dart:4046:6)
#2 Element.findAncestorStateOfType (package:flutter/src/widgets/framework.dart:4093:12)
#3 Navigator.of (package:flutter/src/widgets/navigator.dart:2736:40)
#4 MyDangerousButton.build. (package:helloworld/main.dart:114:19)
Full source code demonstrating the problem:
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Home Page')),
body: Center(
child: ElevatedButton(
child: Text('Open Test Page'),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (_) => TestPage()),
);
},
),
),
);
}
}
class TestPage extends StatefulWidget {
#override
State<TestPage> createState() => _TestPageState();
}
class _TestPageState extends State<TestPage> {
late final Timer timer;
#override
void initState() {
super.initState();
timer = Timer.periodic(Duration(milliseconds: 500), (timer) {
setState(() {});
});
}
#override
void dispose() {
timer.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
final time = DateTime.now().millisecondsSinceEpoch;
return Scaffold(
appBar: AppBar(title: Text('Test Page')),
body: Center(
child: Column(
children: [
Text('Current Time: $time'),
MySafeButton(key: UniqueKey()),
MyDangerousButton(key: UniqueKey()),
],
),
),
);
}
}
class MySafeButton extends StatelessWidget {
const MySafeButton({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return ElevatedButton(
child: Text('Open Dialog Then Pop Safely'),
onPressed: () async {
final navigator = Navigator.of(context);
await showDialog(
context: context,
builder: (_) => AlertDialog(
title: Text('Dialog Title'),
),
);
navigator.pop();
},
);
}
}
class MyDangerousButton extends StatelessWidget {
const MyDangerousButton({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return ElevatedButton(
child: Text('Open Dialog Then Pop Dangerously'),
onPressed: () async {
await showDialog(
context: context,
builder: (_) => AlertDialog(
title: Text('Dialog Title'),
),
);
Navigator.of(context).pop();
},
);
}
}
Flutter ≥ 3.7 answer:
You can now use mounted on a StatelessWidget. This solution will not show linter warning:
onTap: () async {
bool deleteConfirmed = await showModalBottomSheet<bool>(/* open the confirm dialog */);
if (mounted && deleteConfirmed) {
Navigator.of(context).pop();
}
},
Alternatively, you can use context.mounted if outside of the widget.
For various reasons, sometimes the build method of my widgets is called again.
I know that it happens because a parent updated. But this causes undesired effects.
A typical situation where it causes problems is when using FutureBuilder this way:
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: httpCall(),
builder: (context, snapshot) {
// create some layout here
},
);
}
In this example, if the build method were to be called again, it would trigger another HTTP request. Which is undesired.
Considering this, how to deal with the unwanted build? Is there any way to prevent a build call?
The build method is designed in such a way that it should be pure/without side effects. This is because many external factors can trigger a new widget build, such as:
Route pop/push
Screen resize, usually due to keyboard appearance or orientation change
The parent widget recreated its child
An InheritedWidget the widget depends on (Class.of(context) pattern) change
This means that the build method should not trigger an http call or modify any state.
How is this related to the question?
The problem you are facing is that your build method has side effects/is not pure, making extraneous build calls troublesome.
Instead of preventing build calls, you should make your build method pure, so that it can be called anytime without impact.
In the case of your example, you'd transform your widget into a StatefulWidget then extract that HTTP call to the initState of your State:
class Example extends StatefulWidget {
#override
_ExampleState createState() => _ExampleState();
}
class _ExampleState extends State<Example> {
Future<int> future;
#override
void initState() {
future = Future.value(42);
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: future,
builder: (context, snapshot) {
// create some layout here
},
);
}
}
I know this already. I came here because I really want to optimize rebuilds
It is also possible to make a widget capable of rebuilding without forcing its children to build too.
When the instance of a widget stays the same; Flutter purposefully won't rebuild children. It implies that you can cache parts of your widget tree to prevent unnecessary rebuilds.
The easiest way is to use dart const constructors:
#override
Widget build(BuildContext context) {
return const DecoratedBox(
decoration: BoxDecoration(),
child: Text("Hello World"),
);
}
Thanks to that const keyword, the instance of DecoratedBox will stay the same even if the build was called hundreds of times.
But you can achieve the same result manually:
#override
Widget build(BuildContext context) {
final subtree = MyWidget(
child: Text("Hello World")
);
return StreamBuilder<String>(
stream: stream,
initialData: "Foo",
builder: (context, snapshot) {
return Column(
children: <Widget>[
Text(snapshot.data),
subtree,
],
);
},
);
}
In this example when StreamBuilder is notified of new values, subtree won't rebuild even if the StreamBuilder/Column does.
It happens because, thanks to the closure, the instance of MyWidget didn't change.
This pattern is used a lot in animations. Typical uses are AnimatedBuilder and all transitions such as AlignTransition.
You could also store subtree into a field of your class, although less recommended as it breaks the hot-reload feature.
You can prevent unwanted build calling, using these way
Create child Statefull class for individual small part of UI
Use Provider library, so using it you can stop unwanted build method calling
In these below situation build method call
After calling initState
After calling didUpdateWidget
when setState() is called.
when keyboard is open
when screen orientation changed
If Parent widget is build then child widget also rebuild
Flutter also has ValueListenableBuilder<T> class . It allows you to rebuild only some of the widgets necessary for your purpose and skip the expensive widgets.
you can see the documents here ValueListenableBuilder flutter docs
or just the sample code below:
return Scaffold(
appBar: AppBar(
title: Text(widget.title)
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('You have pushed the button this many times:'),
ValueListenableBuilder(
builder: (BuildContext context, int value, Widget child) {
// This builder will only get called when the _counter
// is updated.
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text('$value'),
child,
],
);
},
valueListenable: _counter,
// The child parameter is most helpful if the child is
// expensive to build and does not depend on the value from
// the notifier.
child: goodJob,
)
],
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.plus_one),
onPressed: () => _counter.value += 1,
),
);
One of the easiest ways to avoid unwanted reBuilds that are caused usually by calling setState() in order to update only a specific Widget and not refreshing the whole page, is to cut that part of your code and wrap it as an independent Widget in another Stateful class.
For example in following code, Build method of parent page is called over and over by pressing the FAB button:
import 'package:flutter/material.dart';
void main() {
runApp(TestApp());
}
class TestApp extends StatefulWidget {
#override
_TestAppState createState() => _TestAppState();
}
class _TestAppState extends State<TestApp> {
int c = 0;
#override
Widget build(BuildContext context) {
print('build is called');
return MaterialApp(home: Scaffold(
appBar: AppBar(
title: Text('my test app'),
),
body: Center(child:Text('this is a test page')),
floatingActionButton: FloatingActionButton(
onPressed: (){
setState(() {
c++;
});
},
tooltip: 'Increment',
child: Icon(Icons.wb_incandescent_outlined, color: (c % 2) == 0 ? Colors.white : Colors.black)
)
));
}
}
But if you separate the FloatingActionButton widget in another class with its own life cycle, setState() method does not cause the parent class Build method to re-run:
import 'package:flutter/material.dart';
import 'package:flutter_app_mohsen/widgets/my_widget.dart';
void main() {
runApp(TestApp());
}
class TestApp extends StatefulWidget {
#override
_TestAppState createState() => _TestAppState();
}
class _TestAppState extends State<TestApp> {
int c = 0;
#override
Widget build(BuildContext context) {
print('build is called');
return MaterialApp(home: Scaffold(
appBar: AppBar(
title: Text('my test app'),
),
body: Center(child:Text('this is a test page')),
floatingActionButton: MyWidget(number: c)
));
}
}
and the MyWidget class:
import 'package:flutter/material.dart';
class MyWidget extends StatefulWidget {
int number;
MyWidget({this.number});
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
#override
Widget build(BuildContext context) {
return FloatingActionButton(
onPressed: (){
setState(() {
widget.number++;
});
},
tooltip: 'Increment',
child: Icon(Icons.wb_incandescent_outlined, color: (widget.number % 2) == 0 ? Colors.white : Colors.black)
);
}
}
I just want to share my experience of unwanted widget build mainly due to context but I found a way that is very effective for
Route pop/push
So you need to use Navigator.pushReplacement() so that the context of the previous page has no relation with the upcoming page
Use Navigator.pushReplacement() for navigating from the first page to Second
In second page again we need to use Navigator.pushReplacement()
In appBar we add -
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
Navigator.pushReplacement(
context,
RightToLeft(page: MyHomePage()),
);
},
)
In this way we can optimize our app
You can do something like this:
class Example extends StatefulWidget {
#override
_ExampleState createState() => _ExampleState();
}
class _ExampleState extends State<Example> {
Future<int> future;
#override
void initState() {
future = httpCall();
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: future,
builder: (context, snapshot) {
// create some layout here
},
);
}
void refresh(){
setState((){
future = httpCall();
});
}
}
For various reasons, sometimes the build method of my widgets is called again.
I know that it happens because a parent updated. But this causes undesired effects.
A typical situation where it causes problems is when using FutureBuilder this way:
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: httpCall(),
builder: (context, snapshot) {
// create some layout here
},
);
}
In this example, if the build method were to be called again, it would trigger another HTTP request. Which is undesired.
Considering this, how to deal with the unwanted build? Is there any way to prevent a build call?
The build method is designed in such a way that it should be pure/without side effects. This is because many external factors can trigger a new widget build, such as:
Route pop/push
Screen resize, usually due to keyboard appearance or orientation change
The parent widget recreated its child
An InheritedWidget the widget depends on (Class.of(context) pattern) change
This means that the build method should not trigger an http call or modify any state.
How is this related to the question?
The problem you are facing is that your build method has side effects/is not pure, making extraneous build calls troublesome.
Instead of preventing build calls, you should make your build method pure, so that it can be called anytime without impact.
In the case of your example, you'd transform your widget into a StatefulWidget then extract that HTTP call to the initState of your State:
class Example extends StatefulWidget {
#override
_ExampleState createState() => _ExampleState();
}
class _ExampleState extends State<Example> {
Future<int> future;
#override
void initState() {
future = Future.value(42);
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: future,
builder: (context, snapshot) {
// create some layout here
},
);
}
}
I know this already. I came here because I really want to optimize rebuilds
It is also possible to make a widget capable of rebuilding without forcing its children to build too.
When the instance of a widget stays the same; Flutter purposefully won't rebuild children. It implies that you can cache parts of your widget tree to prevent unnecessary rebuilds.
The easiest way is to use dart const constructors:
#override
Widget build(BuildContext context) {
return const DecoratedBox(
decoration: BoxDecoration(),
child: Text("Hello World"),
);
}
Thanks to that const keyword, the instance of DecoratedBox will stay the same even if the build was called hundreds of times.
But you can achieve the same result manually:
#override
Widget build(BuildContext context) {
final subtree = MyWidget(
child: Text("Hello World")
);
return StreamBuilder<String>(
stream: stream,
initialData: "Foo",
builder: (context, snapshot) {
return Column(
children: <Widget>[
Text(snapshot.data),
subtree,
],
);
},
);
}
In this example when StreamBuilder is notified of new values, subtree won't rebuild even if the StreamBuilder/Column does.
It happens because, thanks to the closure, the instance of MyWidget didn't change.
This pattern is used a lot in animations. Typical uses are AnimatedBuilder and all transitions such as AlignTransition.
You could also store subtree into a field of your class, although less recommended as it breaks the hot-reload feature.
You can prevent unwanted build calling, using these way
Create child Statefull class for individual small part of UI
Use Provider library, so using it you can stop unwanted build method calling
In these below situation build method call
After calling initState
After calling didUpdateWidget
when setState() is called.
when keyboard is open
when screen orientation changed
If Parent widget is build then child widget also rebuild
Flutter also has ValueListenableBuilder<T> class . It allows you to rebuild only some of the widgets necessary for your purpose and skip the expensive widgets.
you can see the documents here ValueListenableBuilder flutter docs
or just the sample code below:
return Scaffold(
appBar: AppBar(
title: Text(widget.title)
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('You have pushed the button this many times:'),
ValueListenableBuilder(
builder: (BuildContext context, int value, Widget child) {
// This builder will only get called when the _counter
// is updated.
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text('$value'),
child,
],
);
},
valueListenable: _counter,
// The child parameter is most helpful if the child is
// expensive to build and does not depend on the value from
// the notifier.
child: goodJob,
)
],
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.plus_one),
onPressed: () => _counter.value += 1,
),
);
One of the easiest ways to avoid unwanted reBuilds that are caused usually by calling setState() in order to update only a specific Widget and not refreshing the whole page, is to cut that part of your code and wrap it as an independent Widget in another Stateful class.
For example in following code, Build method of parent page is called over and over by pressing the FAB button:
import 'package:flutter/material.dart';
void main() {
runApp(TestApp());
}
class TestApp extends StatefulWidget {
#override
_TestAppState createState() => _TestAppState();
}
class _TestAppState extends State<TestApp> {
int c = 0;
#override
Widget build(BuildContext context) {
print('build is called');
return MaterialApp(home: Scaffold(
appBar: AppBar(
title: Text('my test app'),
),
body: Center(child:Text('this is a test page')),
floatingActionButton: FloatingActionButton(
onPressed: (){
setState(() {
c++;
});
},
tooltip: 'Increment',
child: Icon(Icons.wb_incandescent_outlined, color: (c % 2) == 0 ? Colors.white : Colors.black)
)
));
}
}
But if you separate the FloatingActionButton widget in another class with its own life cycle, setState() method does not cause the parent class Build method to re-run:
import 'package:flutter/material.dart';
import 'package:flutter_app_mohsen/widgets/my_widget.dart';
void main() {
runApp(TestApp());
}
class TestApp extends StatefulWidget {
#override
_TestAppState createState() => _TestAppState();
}
class _TestAppState extends State<TestApp> {
int c = 0;
#override
Widget build(BuildContext context) {
print('build is called');
return MaterialApp(home: Scaffold(
appBar: AppBar(
title: Text('my test app'),
),
body: Center(child:Text('this is a test page')),
floatingActionButton: MyWidget(number: c)
));
}
}
and the MyWidget class:
import 'package:flutter/material.dart';
class MyWidget extends StatefulWidget {
int number;
MyWidget({this.number});
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
#override
Widget build(BuildContext context) {
return FloatingActionButton(
onPressed: (){
setState(() {
widget.number++;
});
},
tooltip: 'Increment',
child: Icon(Icons.wb_incandescent_outlined, color: (widget.number % 2) == 0 ? Colors.white : Colors.black)
);
}
}
I just want to share my experience of unwanted widget build mainly due to context but I found a way that is very effective for
Route pop/push
So you need to use Navigator.pushReplacement() so that the context of the previous page has no relation with the upcoming page
Use Navigator.pushReplacement() for navigating from the first page to Second
In second page again we need to use Navigator.pushReplacement()
In appBar we add -
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
Navigator.pushReplacement(
context,
RightToLeft(page: MyHomePage()),
);
},
)
In this way we can optimize our app
You can do something like this:
class Example extends StatefulWidget {
#override
_ExampleState createState() => _ExampleState();
}
class _ExampleState extends State<Example> {
Future<int> future;
#override
void initState() {
future = httpCall();
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: future,
builder: (context, snapshot) {
// create some layout here
},
);
}
void refresh(){
setState((){
future = httpCall();
});
}
}
For various reasons, sometimes the build method of my widgets is called again.
I know that it happens because a parent updated. But this causes undesired effects.
A typical situation where it causes problems is when using FutureBuilder this way:
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: httpCall(),
builder: (context, snapshot) {
// create some layout here
},
);
}
In this example, if the build method were to be called again, it would trigger another HTTP request. Which is undesired.
Considering this, how to deal with the unwanted build? Is there any way to prevent a build call?
The build method is designed in such a way that it should be pure/without side effects. This is because many external factors can trigger a new widget build, such as:
Route pop/push
Screen resize, usually due to keyboard appearance or orientation change
The parent widget recreated its child
An InheritedWidget the widget depends on (Class.of(context) pattern) change
This means that the build method should not trigger an http call or modify any state.
How is this related to the question?
The problem you are facing is that your build method has side effects/is not pure, making extraneous build calls troublesome.
Instead of preventing build calls, you should make your build method pure, so that it can be called anytime without impact.
In the case of your example, you'd transform your widget into a StatefulWidget then extract that HTTP call to the initState of your State:
class Example extends StatefulWidget {
#override
_ExampleState createState() => _ExampleState();
}
class _ExampleState extends State<Example> {
Future<int> future;
#override
void initState() {
future = Future.value(42);
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: future,
builder: (context, snapshot) {
// create some layout here
},
);
}
}
I know this already. I came here because I really want to optimize rebuilds
It is also possible to make a widget capable of rebuilding without forcing its children to build too.
When the instance of a widget stays the same; Flutter purposefully won't rebuild children. It implies that you can cache parts of your widget tree to prevent unnecessary rebuilds.
The easiest way is to use dart const constructors:
#override
Widget build(BuildContext context) {
return const DecoratedBox(
decoration: BoxDecoration(),
child: Text("Hello World"),
);
}
Thanks to that const keyword, the instance of DecoratedBox will stay the same even if the build was called hundreds of times.
But you can achieve the same result manually:
#override
Widget build(BuildContext context) {
final subtree = MyWidget(
child: Text("Hello World")
);
return StreamBuilder<String>(
stream: stream,
initialData: "Foo",
builder: (context, snapshot) {
return Column(
children: <Widget>[
Text(snapshot.data),
subtree,
],
);
},
);
}
In this example when StreamBuilder is notified of new values, subtree won't rebuild even if the StreamBuilder/Column does.
It happens because, thanks to the closure, the instance of MyWidget didn't change.
This pattern is used a lot in animations. Typical uses are AnimatedBuilder and all transitions such as AlignTransition.
You could also store subtree into a field of your class, although less recommended as it breaks the hot-reload feature.
You can prevent unwanted build calling, using these way
Create child Statefull class for individual small part of UI
Use Provider library, so using it you can stop unwanted build method calling
In these below situation build method call
After calling initState
After calling didUpdateWidget
when setState() is called.
when keyboard is open
when screen orientation changed
If Parent widget is build then child widget also rebuild
Flutter also has ValueListenableBuilder<T> class . It allows you to rebuild only some of the widgets necessary for your purpose and skip the expensive widgets.
you can see the documents here ValueListenableBuilder flutter docs
or just the sample code below:
return Scaffold(
appBar: AppBar(
title: Text(widget.title)
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('You have pushed the button this many times:'),
ValueListenableBuilder(
builder: (BuildContext context, int value, Widget child) {
// This builder will only get called when the _counter
// is updated.
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text('$value'),
child,
],
);
},
valueListenable: _counter,
// The child parameter is most helpful if the child is
// expensive to build and does not depend on the value from
// the notifier.
child: goodJob,
)
],
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.plus_one),
onPressed: () => _counter.value += 1,
),
);
One of the easiest ways to avoid unwanted reBuilds that are caused usually by calling setState() in order to update only a specific Widget and not refreshing the whole page, is to cut that part of your code and wrap it as an independent Widget in another Stateful class.
For example in following code, Build method of parent page is called over and over by pressing the FAB button:
import 'package:flutter/material.dart';
void main() {
runApp(TestApp());
}
class TestApp extends StatefulWidget {
#override
_TestAppState createState() => _TestAppState();
}
class _TestAppState extends State<TestApp> {
int c = 0;
#override
Widget build(BuildContext context) {
print('build is called');
return MaterialApp(home: Scaffold(
appBar: AppBar(
title: Text('my test app'),
),
body: Center(child:Text('this is a test page')),
floatingActionButton: FloatingActionButton(
onPressed: (){
setState(() {
c++;
});
},
tooltip: 'Increment',
child: Icon(Icons.wb_incandescent_outlined, color: (c % 2) == 0 ? Colors.white : Colors.black)
)
));
}
}
But if you separate the FloatingActionButton widget in another class with its own life cycle, setState() method does not cause the parent class Build method to re-run:
import 'package:flutter/material.dart';
import 'package:flutter_app_mohsen/widgets/my_widget.dart';
void main() {
runApp(TestApp());
}
class TestApp extends StatefulWidget {
#override
_TestAppState createState() => _TestAppState();
}
class _TestAppState extends State<TestApp> {
int c = 0;
#override
Widget build(BuildContext context) {
print('build is called');
return MaterialApp(home: Scaffold(
appBar: AppBar(
title: Text('my test app'),
),
body: Center(child:Text('this is a test page')),
floatingActionButton: MyWidget(number: c)
));
}
}
and the MyWidget class:
import 'package:flutter/material.dart';
class MyWidget extends StatefulWidget {
int number;
MyWidget({this.number});
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
#override
Widget build(BuildContext context) {
return FloatingActionButton(
onPressed: (){
setState(() {
widget.number++;
});
},
tooltip: 'Increment',
child: Icon(Icons.wb_incandescent_outlined, color: (widget.number % 2) == 0 ? Colors.white : Colors.black)
);
}
}
I just want to share my experience of unwanted widget build mainly due to context but I found a way that is very effective for
Route pop/push
So you need to use Navigator.pushReplacement() so that the context of the previous page has no relation with the upcoming page
Use Navigator.pushReplacement() for navigating from the first page to Second
In second page again we need to use Navigator.pushReplacement()
In appBar we add -
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
Navigator.pushReplacement(
context,
RightToLeft(page: MyHomePage()),
);
},
)
In this way we can optimize our app
You can do something like this:
class Example extends StatefulWidget {
#override
_ExampleState createState() => _ExampleState();
}
class _ExampleState extends State<Example> {
Future<int> future;
#override
void initState() {
future = httpCall();
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: future,
builder: (context, snapshot) {
// create some layout here
},
);
}
void refresh(){
setState((){
future = httpCall();
});
}
}