Introduction
Continuing our discussion on Flutter Architecture, where we explored the Widget, Element, and Render Object trees (Flutter Architecture Made it Easy) and how a widget is rendered on the screen (Understanding Flutter's Rendering Pipeline: From Widgets to Pixels), we now focus on Flutter’s lifecycle.
We will examine lifecycles from the broadest to the most specific, starting with the Application Lifecycle and progressing down to the RenderObject Lifecycle.
When we talk about lifecycles in Flutter, we refer to a broader concept that applies to Elements, State Objects, and Render Objects. To clarify, the entities that have lifecycles in Flutter are:
- The State Object (associated with StatefulWidget)
- The Element
- The RenderObject
- The Application
Surprisingly, Widgets do not have a lifecycle as their immutable nature prevents them from being modified after creation.
Let’s begin with the Application Lifecycle.
Application Lifecycle
Flutter provides two primary mechanisms for managing application lifecycle events: WidgetsBindingObserver and AppLifecycleListener. We'll focus on AppLifecycleListener due to its more organized and thorough approach. It is a listener that detects changes in the application's lifecycle and utilizes AppLifecycleState, an enum that defines the different phases of an application’s lifecycle. The phases are:
Detached
The app is hosted by the Flutter engine but is completely disconnected from the framework. This is the default state before the app starts running.
Triggered when onDetach is called.
Resumed
The app is in the foreground, active, and ready for user interaction.
Called by the onResume. In the past, onStart was also used to trigger this phase, but it seems that it is no longer used.
Inactive
The app is running but transitioning between foreground and background and is not currently interactive.
- Non-Web Desktop: The app is not in the foreground but still has visible windows.
- Web: The app runs in a window or tab without input focus.
- iOS/macOS: The app enters this state during a phone call or while responding to a Touch ID request.
- Android: The app may be partially obscured, in split-screen mode, or interrupted by a system dialog, notification shade, or another view.
onInactive and onShow are responsible for triggering this phase.
Hidden
All views of the app are hidden. This occurs when:
- The app is about to be paused (iOS/Android).
- The app is minimized or placed on an inactive desktop (non-web desktop).
- The app runs in a web browser tab that is no longer visible.
onHide and onRestart are the functions used to trigger this phase
Paused
The app is not visible to the user and is not responding to user input.
onPaused triggers the Paused phase.
Source: https://api.flutter.dev/flutter/widgets/AppLifecycleListener-class.html
State Object Lifecycle
State Object is commonly seen and used by the developer in the StatefulWidget, which is an immutable widget that defines the structure of a stateful component. It includes the createState method, responsible for creating an instance of the associated State object and createElement, in charge of creating an element and mounting it to the widget tree. The StatefulWidget itself does not have a state or a lifecycle, instead, it delegates this responsibility to the State Object.
With that in mind, the lifecycle of a State Object can be described below
createState
This method is triggered when a StatefulWidget is created. It generates the State object associated with the widget.
It can be called multiple times throughout a StatefulWidget's lifecycle. For instance, if the widget is inserted into the tree at multiple locations, Flutter creates a separate State object for each location.
initState()
Called once when the State object is first added to the widget tree. Flutter invokes this method exactly one time for each State object it creates.
didChangeDependencies
Executed immediately after initState and whenever the widget's dependencies change. For example, if a previously referenced InheritedWidget changes, Flutter calls this method to notify the State object of the update.
didUpdateWidget
Triggered when the widget’s configuration changes. If the parent widget rebuilds and provides a new widget with the same runtimeType and Widget.key, the framework updates the widget property of the State object to reference the new widget. This method receives the previous widget as an argument.
Build()
This method is responsible for constructing the widget's UI. It is called following initState and didChangeDependencies or whenever there is a need to update the UI.
It is called in several scenarios, including:
- After initState
- After didUpdateWidget
- After receiving a call to setState
- After a dependency of this State object changes
- After calling deactivate and then reinserting the State object into the tree at another location.
Because it has the potential to be called on every frame, build() should remain efficient. Flutter replaces the subtree beneath this widget with the widget returned by build(), either by updating the existing subtree or by recreating it entirely.
The BuildContext passed as an argument, provides information about the widget’s location within the tree.
setState
Although not strictly part of the lifecycle, setState() plays a crucial role in triggering UI updates. It marks the widget for rebuilding whenever there is a state change.
Element Lifecycle
In Flutter, an element is created for each widget in the widget tree. Elements store information about the widget’s parent, children, and size. If you want to learn more about elements, check out this post.
As for their lifecycle, it is managed by the _ElementLifecycle enum, which defines the different phases an element goes through.
Initial
Element has just been created and still needs to be fully initialized
Active
An element transitions to this state when it is fully initialized and attached to the widget tree. The element spends most of its lifecycle in this state.
Inactive
When it is removed from the widget tree but remains in memory. It still has the potential to be put back on the widget tree. Example: when a parent widget stops incorporating the child in its build method.
Defunct
Element is no longer part of the widget tree and will be removed from memory by the garbage collection.
The Lifecycle
Initial Phase
The framework creates an element by calling createElement().
Active Phase
The element is added to the widget tree when the framework calls mount. Depending on the scenario, it either attaches an existing render object using attachRenderObject , or creates a new one using createRenderObject. At this stage, the element is considered active.
If the parent widget updates, the framework calls update to reconfigure the element with the new widget.
Inactive Phase
At some point, an ancestor might decide to remove this element (or an intermediate ancestor) from the tree, which the ancestor does by calling deactivateChild, removing that element's render object from the tree.
Defunct Phase
If the element does not get reincorporated into the tree by the end of the current animation frame, the framework will call unmount, transitioning to the defunct phase.
RenderObject
Finally, RenderObjects are mutable and manage the size, position, and rendering of their associated elements. Again, If you want to learn more about RenderObjects, refer to this post.
Initialization
Its lifecycle begins in the initialization phase, Usually, who creates a renderObject is a RenderObjectElement. During this phase, the object’s initial state, properties, and relationships within the tree are established.
Layout
This phase involves calculating the size and position of the object. It will determine how the object will fit within the UI.
Painting
The object will translate its information into a visual representation.
Hit Testing
This process identifies which render objects exist at a given position. It is performed using the hitTest.
Disposal
Dispose of the resources of the object.
Conclusion
The Application Lifecycle governs how an app responds to system events, the State Object Lifecycle dictates how stateful widgets manage their state, the Element Lifecycle determines how widgets are mounted and updated within the widget tree, and the RenderObject Lifecycle handles layout, painting, and hit detection.
A deep understanding of these lifecycles empowers developers to optimize widget rebuilding, manage resources effectively, and write more performant Flutter applications. Whether you're designing a dynamic user interface or troubleshooting unexpected behavior, keeping lifecycle management in mind will help you make better architectural decisions.
Sources
Flutter Engineering by Majid Hajian
https://api.flutter.dev/flutter/widgets/WidgetsBindingObserver-class.html
https://api.flutter.dev/flutter/widgets/AppLifecycleListener-class.html
https://api.flutter.dev/flutter/dart-ui/AppLifecycleState.html
https://api.flutter.dev/flutter/widgets/AppLifecycleListener/onPause.html
https://api.flutter.dev/flutter/widgets/AppLifecycleListener/onDetach.html
https://api.flutter.dev/flutter/widgets/AppLifecycleListener/onHide.html
https://api.flutter.dev/flutter/widgets/AppLifecycleListener/onInactive.html
https://api.flutter.dev/flutter/widgets/AppLifecycleListener/onPause.html
https://api.flutter.dev/flutter/widgets/AppLifecycleListener/onRestart.html
https://api.flutter.dev/flutter/widgets/AppLifecycleListener/onResume.html
https://api.flutter.dev/flutter/widgets/AppLifecycleListener/onShow.html
https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html
https://api.flutter.dev/flutter/widgets/State-class.html
https://api.flutter.dev/flutter/widgets/State/initState.html
https://api.flutter.dev/flutter/widgets/State/didChangeDependencies.html
https://api.flutter.dev/flutter/widgets/State/build.html
https://api.flutter.dev/flutter/widgets/Element/mount.html
https://api.flutter.dev/flutter/widgets/Element/attachRenderObject.html
https://api.flutter.dev/flutter/widgets/Transform/createRenderObject.html
https://api.flutter.dev/flutter/widgets/Element/update.html
https://api.flutter.dev/flutter/widgets/Element/deactivateChild.html
https://api.flutter.dev/flutter/widgets/RenderObjectElement-class.html
https://api.flutter.dev/flutter/gestures/HitTestResult-class.html
https://api.flutter.dev/flutter/rendering/RenderProxyBoxWithHitTestBehavior/hitTest.html#:~:text=Determines%20the%20set%20of%20render,this%20one%20from%20being%20hit
Top comments (0)