Flutter’s Stateful Widget Lifecycle – An Amazing Analysis

Flutter operates on two widgets, namely Stateless and Stateful. Both have different methods before the requirement of the user. However, today’s article will point towards Flutter’s Stateful Widget Lifecycle – An Amazing Analysis, its total number of methods, and their detailed aspects with code snippets and examples, etc. So let’s begin…


There are in total 9 states throughout the lifecycle which are as follows:

Let’s head over to each method individually and try to understand their simplified explanations.

1- createState

When a Stateful Widget is called, createState is the first one responsible for creating a mutable state of the Widget at a given location in the tree.

Here, a question might arise:

Why Stateful Widget uses separate methods? Why it isn’t like Stateless?

Answer: A Stateful Widget is mutable and needs to rebuilt once the state changes, i.e. usage of setState whereas, for Stateless, it does not need any updating and is immutable (non-modifiable). For this reason, Stateful Widget uses two methods for a smoother performance vis-à-vis better UI.

Code snippet

import 'package:flutter/material.dart';

class LifeCycleDemo extends StatefulWidget {
  const LifeCycleDemo({Key? key}) : super(key: key);

  @override
  State<LifeCycleDemo> createState() => _LifeCycleDemoState();
}

class _LifeCycleDemoState extends State<LifeCycleDemo> {
  @override
  Widget build(BuildContext context) {
    return Scaffold();
  }
}

Note: Every time this Widget tree gets called, this state will be recreated, as a fresh object.

2- mounted

Mounted is treated as a bool variable by default, highlighting whether the state object is currently in the widget tree or not, and associates it with a BuildContext. When the state gets created, mounted is the first state that is being initialized.

Code snippet

  if(mounted) {
/// some task to perform
}
}

Note: It is better to use setState inside the mounted property to avoid any crashes.

3- initState

This method is called when a Stateful Widget is inserted into the widget tree for the first time. It is only called once and initializes the Widget’s state.

Secondly, initState is responsible for performing one-time-initialization tasks such as object creation, animation listeners, notification events, asynchronous tasks, Tabbar events, and so on…

Other than that, one such example could be:

Code snippet

late TextEditingController _editingController;

@override
void initState() {
// TODO: implement initState

_editingController = TextEditingController();
super.initState();
}

This method gets called right after mounted.

4- didChangedDependencies

This method is called when the widget’s dependencies change just after being called by the framework. This happens if the widget is inserted into the widget tree or gets a new widget during the running phase.

Additionally, didChangedDependencies is usually used to respond to changes required by the InheritedWidget, or changes to the widget’s dependencies passed as an argument.

Apart from that, under Flutter’s Stateful Widget Lifecycle, it is safe to call BuildContext.dependOnInheritedWidgetOfExactType from this method.

An example could be:

  • Keypad appearance and vice versa
import 'package:flutter/material.dart';

class DemoClass extends StatefulWidget {
  const DemoClass({Key? key, required this.data}) : super(key: key);
  
  final Widget data;

  @override
  _DemoClassState createState() => _DemoClassState();
}

class _DemoClassState extends State<DemoClass> {
  String? _author;

  @override
  void initState() {
    // TODO: implement initState
    _author = widget.data.toString();
    super.initState();
  }

  @override
  void didChangeDependencies() {
    // TODO: implement didChangeDependencies
    _author = widget.data.toString();
    super.didChangeDependencies();
  }

  @override
  Widget build(BuildContext context) {
    return Material(
      child: Center(
        child: Text(_author!),
      ),
    );
  }
}

Note: It is a best practice to avoid expensive operations under this method since it may be frequently called. Instead, heavy operations must be called inside the initState or build method.

5- build

When the framework surpasses didChangedDependencies, by this time, it has a reliable context associated with the method itself, returned by a Widget. When setState gets called, each time this method gets rebuilt followed by the modification the user made.

At this time, since our Widget will depend on InheritedWidget, that’s when we could make use of the following properties:

- MediaQuery.of(context).size.width
- Theme.of(context).textTheme.largeText;
- OrientationBuilder

It must have a return Widget to show the visual appearance of an App.

Code snippet

@override
Widget build(BuildContext context) {
  return MaterialApp(
    debugShowCheckedModeBanner: true,
    home: Scaffold(
      appBar: AppBar(
        title: const Text("Code sample"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [

             /// Custom Widgets
          ],
        ),
      ),
    ),
  );
}

6- didUpdateWidget

When an update of data is needed at a certain point, this state comes to the rescue, i.e. a variable (having a runtime type: string) should be assigned a new value (same type) requested by the parent widget. The framework then refers to the new value assigned to the variable and calls this method with the previous widget treated as an argument.

Note: The build method gets called after didUpdateWidget, which means any calls to setState in didUpdateWidget are duplicated.

Code snippet

// Never call the updated event directly.

@override
void didUpdateWidget(covariant MyApp oldWidget) {
  // TODO: implement didUpdateWidget
  super.didUpdateWidget(oldWidget);
}

// We must be assure about previous and new state events

@override
void didUpdateWidget(covariant MyApp oldWidget) {
  super.didUpdateWidget(oldWidget);
  if (widget.counterValue != oldWidget.counterValue ) {

     // Call the event here
  }
}

Note: It is best to check for any updates first, then call the respective event.

7- deactivate

This method is called when the Stateful Widget is temporarily removed from the widget tree. This can occur if a widget is popped from the navigator stack or replaced by some other Widget. Such as making use of the command,

- Navigator.pushReplacementNamed('');
- Navigator.pop(context) * 2

Furthermore, the deactivate method can be used to cancel ongoing operations/subscriptions that were started by initState, generally to avoid memory leaks caused by not canceling the above events.

In most cases, this can be recovered by the state itself. It depends on what exactly the events are.

Code snippet

import 'dart:async';
import 'package:flutter/material.dart';

class DemoClass extends StatefulWidget {
  const DemoClass({Key? key}) : super(key: key);

  @override
  _DemoClassState createState() => _DemoClassState();
}

class _DemoClassState extends State<DemoClass> {
  Timer? _timer;

  @override
  void initState() {
    // TODO: implement initState
    _timer =Timer.periodic(const Duration(seconds: 5), (value) {
      debugPrint("timer event called");
    });
    super.initState();
  }
  
  @override
  void deactivate() {
    // TODO: implement deactivate
    _timer?.cancel();
    super.deactivate();
  }
  
  @override
  Widget build(BuildContext context) {
    return const Material();
  }
}

Besides this, more such examples could be the following:

  • ListB gets a copy of ListA
  • A function that triggers the ‘Delete All’ event
  • Flutter’s App Life Cycle Events (inActive, pause, etc.)

8- dispose

The permanent removal of the state is associated with the above method. In addition, any stream subscription, listeners, animation controllers, and textEdittingController must be stopped under the dispose method.

The State cannot be recovered if the State is deleted from the tree.

  • For a more in-depth explanation, let’s view both methods via differences.
deactivate()dispose()
The deactivate() method is called when the widget is removed but might be inserted later.The dispose() method is called when the widget is removed from the widget tree permanently.
It is called before the widget is removed from the widget tree, but after build method has been called.It is typically used to release resources or perform cleanup tasks associated with the widget.
This method is useful when the widget wants to do something when it is temporarily removed, but it may be inserted back without being recreated.This method is called automatically by the framework when a widget is removed from the widget tree.
It is not guaranteed that dispose will be called immediately after deactivate.Common use cases include: unsubscribing from streams and closing database connections.
Common use cases include: pausing animations, disconnecting from a service, saving temporary state, canceling network requests, etc.Furthermore, cleaning up any resources that were allocated during the widget’s lifecycle would also help.

9- !mounted

At this point, the widget is completely disposed of by the widget tree and remounting cannot be possible.


Wrapping up

In wrapping up our exploration of Flutter’s Stateful Widget Lifecycle, we delved into a comprehensive understanding of the various lifecycle methods, their specific roles, and the corresponding scenarios where they prove valuable. Throughout this article, we not only provided detailed explanations but also accompanied each concept with illustrative code snippets to enhance clarity.

However, should you feel that certain aspects require further elaboration or if any information appears to need more detail, I encourage you to share your thoughts in the comments section. Your feedback is immensely valuable, and I am eagerly waiting to respond to any inquiries or suggestions you may have.

– Other Blog posts —

Thanks for your precious time.

Leave a Comment