App Lifecycle in Flutter

You may be familiar with Flutter, but knowing Flutter Basics is one of the major steps to better development. You should know how the app lifecycle is created, rendered, updated, and disposed of. This knowledge can help you develop efficient and robust applications.

If you have developed apps for Android or iOS, you must have heard of app life cycle states. Like active, inactive, suspending, etc. Today we will discuss the app lifecycles in Flutter. In Flutter, we majorly discuss the lifecycle in two parts, the Lifecycle of Stateful Widgets and the Lifecycle of the app.

App Lifecycle in Flutter ➰

The App Lifecycle notifies us of the application’s current state. There are 4 possible states of the Flutter application. The AppLifeCycle enum provides us the following options:

  • detached: The application is still hosted on FlutterEngine(The FlutterEngine is the container through which Dart code can be run in an Android application). Here the engine is running without a view, it is either building a view or has no view to show.
  • inactive: The application is inactive and is not receiving user input. On iOS, this may mean that the app is in transition due to a phone call, touchID, etc. On Android, this means that either the app is on split-screen, a phone call, system dialog, etc.
  • paused: The app is currently not visible to the user and is running in the background, it’s not receiving any user input.
  • resumed: The app is visible to the user and is receiving user input.

Now, that we have basic knowledge of the App Lifecycle states let’s take a look at how to listen to these events.

Listening to App Lifecycle Events 👂

By default we can’t listen to the App Lifecycle events we need to use WidgetsBindingObserver class. We can do it in Stateful widgets like this:


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


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

class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
...}

We can do the same using Stateless widgets:

class MyApp extends StatelessWidget with WidgetsBindingObserver{...}

At this point also we would not be receiving any App Lifecycle events, we need to add the current class (this) as an observer to the WidgetsBinding instance. Note that we have to manually remove the observer too. The code for the Stateful widget would look like this:

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance!.addObserver(this);
  }

  @override
  void dispose() {
    WidgetsBinding.instance!.removeObserver(this);
    super.dispose();
  }

In Stateful widgets the initState and dispose are the perfect place to add and remove the observer. Here, addObserver adds the current context to the observer’s list and the removeObserver removes the current context from the observer’s list.

To do the same in Stateless widgets we will have to find a workaround. We can add the observer in the constructor and remove the observer when the back button is pressed. This solution does not handle all the possible cases, if you have a better solution please let me know. The code for adding and removing observers in the Stateless widget will be:

import 'package:flutter/material.dart';

class HomePage extends StatelessWidget with WidgetsBindingObserver {
  HomePage({Key? key}) : super(key: key) {
    WidgetsBinding.instance!.addObserver(this);
  }


  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: () async {
        WidgetsBinding.instance!.removeObserver(this);
        return true;
      },
      child: Scaffold(),
    );
  }
}

Now both Stateful and Stateless widgets are capable of listening to the lifecycle events. We can now simply override the didChangeAppLifecycleState method.

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.resumed) {
      print("Resumed");
    }
    if (state == AppLifecycleState.detached) {
      print("Detached");
    }
    if (state == AppLifecycleState.inactive) {
      print("Inactive");
    }
    if (state == AppLifecycleState.paused) {
      print("Paused");
    }
  }

So like this, we can easily listen to app events and get updated on app-level updates. Now you can get notified when your app moves into the background, get detached, or comes back to the foreground. Now let’s look at the lifecycle of the stateful widget.

LifeCycle of Stateful Widget in Flutter 🧬

In the Flutter application, every element is a widget thus the state lifecycle depends upon the type of widget you use. In Flutter majorly there are 2 types of widgets:

Stateless Widget

Stateless Widgets are those widgets that don’t change state at runtime it has an immutable state. These widgets are permanent and do not change their state once build. These are widgets that do not need to change their state at any point in time. eg: button, listItem, etc.

import 'package:flutter/material.dart';

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

Stateful Widget

Stateful Widgets are those widgets that can change their state at runtime, i.e it has a mutable state. These widgets are non-permanent thus their states change when their properties are modified. Here, to update the state use the setState((){}) function.

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

In the Stateful Widget, we are provided with the lifecycle methods. So we will now focus on Stateful widgets as they deal with the state. Let’s take a look at the methods provided.

createState()

This method is invoked when we create a Stateful Widget. createState() creates the mutable state for this widget at a given location in the tree.

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  @override
  State<MyHomePage> createState() => _MyHomePageState();
 
}

Rest all the Stateful Widget lifecycle methods are overridden inside the State part of the class i.e:

class _MyHomePageState extends State<MyHomePage>{}

initState()

This is called once the createState() function is called. The initState() is only executed once. This code runs before the widgets are rendered so the context is not valid at this point.

This method needs to call super.initState(). Here initialization of variables, stream, animation controller, scroll controller, etc is performed.

@override
void initState(){
  // Initialize your objects here
  super.initState();
}

didChangeDependencies()

This method is invoked directly after the initState(). didChangeDependencies() is mainly called when a dependency of this State class changes. This is rarely overridden as other packages provide such features with optimization.

Example: If the previous call to build referenced an InheritedWidget that later changed, the framework would call this method to notify this object about the change.

flutter.dev
@override
void didChangeDependencies() {
  super.didChangeDependencies();
} 

build()

This function defines the user interface of this Stateful Widget. It is mandatory to implement this function. The framework can call this function in any of these cases:

The framework replaces the subtree below this widget with the widget returned by this method.

@override
Widget build(BuildContext context) {
  return Scaffold();
}

didUpdateWidget()

Used when parent widget configuration changes. If the parent’s properties change so we need to update the current child widget, which is when this function gets invoked. It’s mostly incurred in situations when we hot reload our application while debugging.

@override
void didUpdateWidget(covariant Trying oldWidget) {
   super.didUpdateWidget(oldWidget);
}

deactivate() and activate()

The deactivate() is called when the widget is removed from the tree temporarily, it may join the widget tree at a future point. The deactivated widget is moved back into the widget tree using the activate() method. These functions are not used that frequently.

  @override
  void deactivate() {
    // TODO: implement deactivate
    super.deactivate();
  }

  @override
  void activate() {
    // TODO: implement activate
    super.activate();
  }

dispose()

A frequently used function, is called to release all the resources held by the current widget. Here, you unsubscribe from all events that you manually listen to like streams, controllers, timers, etc. Usually disposing of the values initialized in initState().

@override
void dispose() {
  // TODO: implement dispose
  super.dispose();
}

Conclusion 💫

In this post, I have elaborately explained the two types of lifecycles in Flutter. We looked at App Lifecycle and Stateful Widget lifecycle. I hope now you can override all these functions with a concrete foundation of these functions.

If you have any queries you can comment below and I will be happy to help you. If you are new to Flutter check my post on how to install Flutter on windows?

3 comments
  1. Very useful post and the whole branch about Flutter, I read a few posts and there are really helpful to understand some topics. It is a great help on my flutter learning journey. Keep it going!

  2. My app has a Main() which calls 2 routes. I set up didChangeAppLifecycleState() in Main and one of the Routes. When in that route I set the app as Inactive, Paused, and Resumed, I notice that both the didChangeAppLifecycleState()’s are activated. Does this mean I can safely activate the didChangeAppLifecycle() in Main and be confident I can catch all live cycle changes throughout the app?

Leave a Reply

Your email address will not be published. Required fields are marked *

Previous Post
StreamBuilder in Flutter

Complete guide to StreamBuilder in Flutter

Next Post
GetX in Flutter

Complete guide to GetX in Flutter

Related Posts