How to prevent rebuild stateful widget in flutter [duplicate] - flutter

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();
});
}
}

Related

Is it correct to add event to Bloc in initState or didUpdateWidget?

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());
},
),
);
}
}

Navigate part of screen from drawer

let's say I have an app with the following setup:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Container(
color: Colors.grey[200],
child: Row(
children: [
MainMenu(),
Expanded(child: MainLoginScreen()),
],
),
));
}
}
I would like to know how can I navigate only the MainLoginScreen widget from the MainMenu with any .push() method.
(I found a way to navigate from a context inside the mainloginscreen,by wrapping it with a MaterialApp widget, but what if I want to use the MainMenu widget instead, which has another context)
There is a general agreement that a 'screen' is a topmost widget in the route. An instance of 'screen' is what you pass to Navigator.of(context).push(MaterialPageRoute(builder: (context) => HereGoesTheScreen()). So if it is under Scaffold, it is not a screen. That said, here are the options:
1. If you want to use navigation with 'back' button
Use different screens. To avoid code duplication, create MenuAndContentScreen class:
class MenuAndContentScreen extends StatelessWidget {
final Widget child;
MenuAndContentScreen({
required this.child,
});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Container(
color: Colors.grey[200],
child: Row(
children: [
MainMenu(),
Expanded(child: child),
],
),
),
);
}
}
Then for each screen create a pair of a screen and a nested widget:
class MainLoginScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MenuAndContentScreen(
child: MainLoginWidget(),
);
}
}
class MainLoginWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
// Here goes the screen content.
}
}
2. If you do not need navigation with 'back' button
You may use IndexedStack widget. It can contain multiple widgets with only one visible at a time.
class MenuAndContentScreen extends StatefulWidget {
#override
_MenuAndContentScreenState createState() => _MenuAndContentScreenState(
initialContentIndex: 0,
);
}
class _MenuAndContentScreenState extends State<MenuAndContentScreen> {
int _index;
_MainMenuAndContentScreenState({
required int initialContentIndex,
}) : _contentIndex = initialContentIndex;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Container(
color: Colors.grey[200],
child: Row(
children: [
MainMenu(
// A callback that will be triggered somewhere down the menu
// when an item is tapped.
setContentIndex: _setContentIndex,
),
Expanded(
child: IndexedStack(
index: _contentIndex,
children: [
MainLoginWidget(),
SomeOtherContentWidget(),
],
),
),
],
),
),
);
}
void _setContentIndex(int index) {
setState(() {
_contentIndex = index;
});
}
}
The first way is generally preferred as it is declrative which is a major idea in Flutter. When you have the entire widget tree statically declared, less things can go wrong and need to be tracked. Once you feel it, it really is a pleasure. And if you want to avoid back navigation, use replacement as ahmetakil has suggested in a comment: Navigator.of(context).pushReplacement(...)
The second way is mostly used when MainMenu needs to hold some state that needs to be preserved between views so we choose to have one screen with interchangeable content.
3. Using a nested Navigator widget
As you specifically asked about a nested Navigator widget, you may use it instead of IndexedStack:
class MenuAndContentScreen extends StatefulWidget {
#override
_MenuAndContentScreenState createState() => _MenuAndContentScreenState();
}
class _MenuAndContentScreenState extends State<MenuAndContentScreen> {
final _navigatorKey = GlobalKey();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Container(
color: Colors.grey[200],
child: Row(
children: [
MainMenu(
navigatorKey: _navigatorKey,
),
Expanded(
child: Navigator(
key: _navigatorKey,
onGenerateRoute: ...
),
),
],
),
),
);
}
}
// Then somewhere in MainMenu:
final anotherContext = navigatorKey.currentContext;
Navigator.of(anotherContext).push(...);
This should do the trick, however it is a bad practice because:
MainMenu knows that a particular Navigator exists and it should interact with it. It is better to either abstract this knowledge with a callback as in (2) or do not use a specific navigator as in (1). Flutter is really about passing information down the tree and not up.
At some point you would like to highlight the active item in MainMenu, but it is hard for MainMenu to know which widget is currently in the Navigator. This would add yet another non-down interaction.
For such interaction there is BLoC pattern
In Flutter, BLoC stands for Business Logic Component. In its simpliest form it is a plain object that is created in the parent widget and then passed down to MainMenu and Navigator, these widgets may then send events through it and listen on it.
class CurrentPageBloc {
// int is an example. You may use String, enum or whatever
// to identify pages.
final _outCurrentPageController = BehaviorSubject<int>();
Stream<int> _outCurrentPage => _outCurrentPageController.stream;
void setCurrentPage(int page) {
_outCurrentPageController.sink.add(page);
}
void dispose() {
_outCurrentPageController.close();
}
}
class MenuAndContentScreen extends StatefulWidget {
#override
_MenuAndContentScreenState createState() => _MenuAndContentScreenState();
}
class _MenuAndContentScreenState extends State<MenuAndContentScreen> {
final _currentPageBloc = CurrentPageBloc();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Container(
color: Colors.grey[200],
child: Row(
children: [
MainMenu(
currentPageBloc: _currentPageBloc,
),
Expanded(
child: ContentWidget(
currentPageBloc: _currentPageBloc,
onGenerateRoute: ...
),
),
],
),
),
);
}
#override
void dispose() {
_currentPageBloc.dispose();
}
}
// Then in MainMenu:
currentPageBloc.setCurrentPage(1);
// Then in ContentWidget's state:
final _navigatorKey = GlobalKey();
late final StreamSubscription _subscription;
#override
void initState() {
super.initState();
_subscription = widget.currentPageBloc.outCurrentPage.listen(_setCurrentPage);
}
#override
Widget build(BuildContext context) {
return Navigator(
key: _navigatorKey,
// Everything else.
);
}
void _setCurrentPage(int currentPage) {
// Can't use this.context, because the Navigator's context is down the tree.
final anotherContext = navigatorKey?.currentContext;
if (anotherContext != null) { // null if the event is emitted before the first build.
Navigator.of(anotherContext).push(...); // Use currentPage
}
}
#override
void dispose() {
_subscription.cancel();
}
This has advantages:
MainMenu does not know who will receive the event, if anybody.
Any number of listeners may listen on such events.
However, there is still a fundamental flaw with Navigator. It can be navigated without MainMenu knowledge using 'back' button or by its internal widgets. So there is no single variable that knows which page is showing now. To highlight the active menu item, you would query the Navigator's stack which eliminates the benefits of BLoC.
For all these reasons I still suggest one of the first two solutions.

Flutter rebuilds previous routes when focusing on TextField [duplicate]

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();
});
}
}

Deeper understanding: Stateful widget is always rebuilt even though I am in different view? [duplicate]

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();
});
}
}

Flutter StatefulWidget hierarchy [duplicate]

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();
});
}
}