Mastering Flutter & Android Lifecycle: A Developer's Guide

Flutter has become the go-to framework for building cross-platform applications, but understanding the lifecycle of an app remains crucial—especially for developers coming from a native Android background. In Android, we rely on specific lifecycle methods like onResume(), onPause(), onStop(), and onDestroy(). However, in Flutter, the app lifecycle is managed differently.

In this article, we’ll explore Android lifecycle methods and their equivalents in Flutter, along with real-world use cases.

Android App Lifecycle Overview

Android applications follow a well-defined lifecycle, controlled by the Activity class. Here’s how the primary lifecycle methods work:

Lifecycle MethodDescription
onCreate()Called when the activity is first created.
onStart()Called when the activity becomes visible.
onResume()Called when the user starts interacting with the activity.
onPause()Called when the activity is partially obscured (e.g., another activity is opening).
onStop()Called when the activity is no longer visible.
onRestart()Called when the activity is coming back to the foreground.
onDestroy()Called before the activity is destroyed.

Flutter’s Lifecycle Approach

Unlike Android, Flutter does not have an Activity-based lifecycle. Instead, Flutter apps use WidgetsBindingObserver to track lifecycle changes at the app level.

Flutter provides four main lifecycle states:

Flutter Lifecycle StateEquivalent Android Lifecycle
AppLifecycleState.resumedonResume()
AppLifecycleState.inactivePart of onPause()
AppLifecycleState.pausedonStop()
AppLifecycleState.detachedonDestroy()

Understanding initState(), dispose(), and Other Methods in Flutter

Flutter's StatefulWidget lifecycle consists of several key methods:

MethodDescription
initState()Called once when the widget is inserted into the widget tree. Ideal for initializing resources like controllers, animations, or network calls.
didChangeDependencies()Called when the widget’s dependencies change (e.g., inherited widgets update).
build()Called every time the widget needs to be rebuilt. Returns the UI.
didUpdateWidget()Called when the parent widget rebuilds and provides a new instance of the same widget. Useful for responding to changes in the widget properties.
deactivate()Called when the widget is removed from the tree but might be reinserted later.
dispose()Called when the widget is permanently removed. Used for cleanup, like closing streams or disposing of controllers.

Example: Using initState() and dispose() in Flutter

import 'package:flutter/material.dart';

class ExampleStatefulWidget extends StatefulWidget {
  @override
  _ExampleStatefulWidgetState createState() => _ExampleStatefulWidgetState();
}

class _ExampleStatefulWidgetState extends State<ExampleStatefulWidget> {
  late TextEditingController _controller;

  @override
  void initState() {
    super.initState();
    _controller = TextEditingController(); // Initializing controller
    print("Widget initialized");
  }

  @override
  void dispose() {
    _controller.dispose(); // Cleaning up resources
    print("Widget disposed");
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Lifecycle Example")),
      body: Padding(
        padding: EdgeInsets.all(16.0),
        child: TextField(controller: _controller),
      ),
    );
  }
}

Implementing Lifecycle Listening in Flutter

To listen to app lifecycle changes in Flutter, implement the WidgetsBindingObserver class as shown below:

import 'package:flutter/material.dart';

class AppLifecycleListener extends StatefulWidget {
  @override
  _AppLifecycleListenerState createState() => _AppLifecycleListenerState();
}

class _AppLifecycleListenerState extends State<AppLifecycleListener> with WidgetsBindingObserver {
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

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

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    switch (state) {
      case AppLifecycleState.resumed:
        debugPrint("App is in foreground");
        break;
      case AppLifecycleState.inactive:
        debugPrint("App is inactive");
        break;
      case AppLifecycleState.paused:
        debugPrint("App is in background");
        break;
      case AppLifecycleState.detached:
        debugPrint("App is terminated");
        break;
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(child: Text("Listening to App Lifecycle")),
    );
  }
}

Real-World Use Cases

1. Handling Background Tasks

  • When an app moves to the background (paused state), stop active tasks like video playback or data fetching.

  • When the app resumes, restart those tasks.

2. Detecting User Inactivity

  • If a user goes inactive (inactive state), you can pause UI updates or disable certain features temporarily.

3. Preventing Data Loss

  • Save user progress when paused or detached is triggered to prevent data loss if the app is closed.

Conclusion

Understanding app lifecycle behavior is essential for optimizing app performance and user experience. While Android follows an Activity-based lifecycle, Flutter provides AppLifecycleState with WidgetsBindingObserver. Additionally, knowing how to manage widget lifecycle methods like initState() and dispose() is critical for efficient state management.

By leveraging these lifecycle events, we can build more efficient and responsive applications across platforms.

Let me know if you have any questions or need further clarifications! 🚀

1
Subscribe to my newsletter

Read articles from Anmol Singh Tuteja directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Anmol Singh Tuteja
Anmol Singh Tuteja