Understanding Flutter Widget Lifecycle for Effective App Development
A Guide to Utilizing Widget Lifecycle Methods for Efficient Flutter Development
In general terms, lifecycle represents the stage from its creation until its destruction. In the context of flutter, it's the same. Today we will discuss on Widget lifecycle (Don’t confuse it with the App lifecycle).
As a flutter developer how we should utilize those lifecycle methods is the primary intent of this article so let’s move to the fun part.
createState()
When we create a stateful widget in Flutter, the createState
method is immediately called to return a state instance of the associated class.
This is essential because, in Flutter, everything is immutable (including stateful widgets). It is important to separate the mutability task and delegate it to the state class. This way, Flutter can make immutable classes look and work like mutable ones, and we can see all the changes in the UI.
This also has a performance benefit, as it is easier for Flutter to change a state variable and reflect the change in an optimized manner than to recreate the entire widget tree due to small changes.
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => new _HomePageState();
}
initState()
This is the first method called after-state creation. And it is called only once. This method can be used to:
Initilaize late variables
Subscribe to stream etc
@override
initState() {
super.initState();
_controller=TextEditingController();
myStream.listen((data) {});
}
didChangeDependencies
This method is called immediately after the initState() for the first time and later on when its dependency changes.
So what exactly is its dependency? Let's say we use MediaQuery.of in a widget then we can say our widget depends on MediaQuery. So whenever MediaQuery updates this method gets triggered.
So we can say if our widget depends on Inherited Widget
(media query, theme, providers, etc) and whenever those inherited widget push updates it will trigger this method.
After this build()
method will be triggered so as a developer there are not many scenarios where we may have to use custom logic here but as per official documentation it says,
to do some expensive work (e.g., network fetches) when their dependencies change, and that work would be too expensive to do for every build.
For eg: From your Inherited Widget
you may receive some value and need to do some expensive operation based on that value and show it in UI. You may not want to do this in the build method sodidChangeDependencies
will be the perfect spot for this.
didUpdateWidget
Let's take an example to understand this. You have a Container
which is of Red color and with a button tap you change it to Blue.
Here both the old and new object instances are of the same type runtimeType
but the data is different so in this case this method gets triggered. It receives old widgets too didUpdateWidget(Widget oldWidget)
.
@override
void didUpdateWidget(Widget oldWidget) {
if (oldWidget.color != widget.color) {
/// Your logic
}
}
How can we utilize it?
One possible scenario could be used in animation to transition between values.
Another could be a specific use case based on your app requirement, based on new widget value do some different tasks for eg: subscribe to a new stream and un-subscribe to an earlier stream.
Since build()
method will be called after this method so it's redundant to call setState((){})
in this method.
build()
This is the important method where, as a programmer, we need to return the widget that we want to show to the user.
Also, this method can get triggered multiple times so it’s not a good practice to perform expensive tasks inside this method.
This method can potentially be called in every frame and should not have any side effects beyond building a widget.
setState((){})
setState((){}) is a method that takes a method as a parameter and internally calls markNeedsBuild()
method to trigger a new build.
/// Implementation of setState() in flutter framework
@protected
void setState(VoidCallback fn) {
final Object? result = fn() as dynamic;
_element!.markNeedsBuild();
}
Here we can see the internal implementation of setState. It first runs the method that we passed and triggers markNeedsBuild()
. We can derive another thing from this implementation code that
- we should not pass an async function to setState as internally it won’t wait for the future to resolve so we won’t see the desired UI reflection.
So as a developer, we can use this method to notify Flutter that something has changed and please update the UI.
setState(() {
/// New State values
color=Colors.red;
});
dispose()
dispose() method is called when the state object is removed. This is the last method to get triggered in the lifecycle.
So as a developer, we can:
Unsubscribe to streams
Dispose of the controllers
Stop animations etc
So these are the most common and must-know lifecycle methods in Flutter.
Since stateless widget doesn’t have its own mutable state (obvious from name) some lifecycle methods eg
initState
,dispose
etc are missing here.