DEV Community

João Pimenta
João Pimenta

Posted on

Mastering Flutter Lifecycles: From Application to Render Objects

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.

Image description

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

Image description

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.

Image description

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.

Image description

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.

Image description

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)