Related
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();
});
}
}
I started Flutter recently and my app required bottom navigation. I have created bottom navigation and manage to access the child widget based on the tab selected.
Under the child widget there is drop down selection where I can change the bottom navigation text in one of the tabs for different selections.
I have tried a few days but still could not figure out how the child widget can change the text.
I have tried callback but cannot get it work. I have tried navigation.push - material page route but it rebuild the whole widget and my selection gone. I have also tried to use GlobalKey or Sharedpreference to capture my selection so that when it rebuild, it will use back the stored selection but I couldn't get it work.
I only wish to change the bottom navigation text in one of the text from child widget drop down selection.
Which is the best method to achieve this?
I would recommend you try to use the bloc pattern with a StreamBuilder. I have an example below. Regardless, in the example there is a stateful widget, a bloc, and a data class. Try to understand this code and modify it to your needs.
import 'package:flutter/material.dart';
import 'dart:async';
class StreamScaffold extends StatefulWidget {
#override
_StreamScaffoldState createState() => _StreamScaffoldState();
}
class _StreamScaffoldState extends State<StreamScaffold> {
ScaffoldDataBloc bloc;
#override
void initState() {
super.initState();
bloc = ScaffoldDataBloc();
}
#override
Widget build(BuildContext context) {
return StreamBuilder<ScaffoldDataState>(
stream: bloc.stream, // The stream we want to listen to.
initialData: bloc.initial(), // The initial data the stream provides.
builder: (context, snapshot) {
ScaffoldDataState state = snapshot.data;
Widget page;
if (state.index == 0) {
// TODO separate this into its own widget, this is messy.
page = Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
onPressed: () => bloc.updateText(state,"Sales"),
child: Text("Set text to Sales")
),
RaisedButton(
onPressed: () => bloc.updateText(state, "Purchases"),
child: Text("Set text to Purchases"),
)
]),
);
}
if (state.index == 1) {
// TODO separate this into its own widget, this is messy.
page = Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
onPressed: () => bloc.updateText(state, "Stock"),
child: Text("Set text to Stock"),
),
RaisedButton(
onPressed: () => bloc.updateText(state, "Budget"),
child: Text("Set text to Budget"),
)
]));
}
return Scaffold(
body: page,
bottomNavigationBar: BottomNavigationBar(
currentIndex: state.index,
onTap: (int) => bloc.updateIndex(state, int),
items: [
BottomNavigationBarItem(
icon: Icon(Icons.play_arrow),
// Obtain the text from the state
title: Text(state.variableText)),
BottomNavigationBarItem(
icon: Icon(Icons.play_arrow), title: Text("Test")),
]),
);
});
}
#override
void dispose() {
super.dispose();
bloc.dispose();
}
}
// A data class to hold the required data.
class ScaffoldDataState {
int index;
String variableText;
ScaffoldDataState({this.index = 0, this.variableText = "Hello"});
}
// A bloc to handle updates of the state.
class ScaffoldDataBloc {
StreamController<ScaffoldDataState> scaffoldDataStateController = StreamController<ScaffoldDataState>();
Sink get updateScaffoldDataState => scaffoldDataStateController.sink;
Stream<ScaffoldDataState> get stream => scaffoldDataStateController.stream;
ScaffoldDataBloc();
ScaffoldDataState initial() {
return ScaffoldDataState();
}
void dispose() {
scaffoldDataStateController.close();
}
// Needs to be called every time a change should happen in the UI
// Add updated states into the Sink to get the Stream to update.
void _update(ScaffoldDataState state) {
updateScaffoldDataState.add(state);
}
// Specific methods for updating the different fields in the state object
void updateText(ScaffoldDataState state, String text) {
state.variableText = text;
_update(state);
}
void updateIndex(ScaffoldDataState state, int index) {
state.index = index;
_update(state);
}
}
Hope it helps!
Additional Questions from comment:
The easiest solution would be to simply pass the bloc as a parameter to the widget. Create a new dart file in your project, create a StatelessWidget there, create the code for the page in the build method. Note: it would make sense for you to separate the bloc into its own file along with the data class.
import 'package:flutter/material.dart';
// Import the file where the bloc and data class is located
// You have to have a similar import in the parent widget.
// Your dart files should be located in the lib folder, hit ctrl+space for
// suggestions while writing an import, or alt+enter on a unimported class.
import 'package:playground/scaffold_in_stream_builder.dart';
class ChildPage extends StatelessWidget {
final ScaffoldDataBloc bloc;
final ScaffoldDataState state;
const ChildPage({Key key, this.bloc, this.state}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(); // TODO replace with your page
}
}
However, if the these child widgets get their own children in separate files it would be better to use a InheritedWidget instead, with the bloc and state. This avoids "passing state down". See this article on inherited widgets
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();
});
}
}