Flutter – Widget lifecycle events

The first question for a beginner in Flutter and who already knows Android or iOS would be where is my onCreate or viewDidLoad ? What is the initial state in Flutter do write my business logic? If you’re looking for an answer then this blog might help you to understand the Widget Life cycle in Flutter.

Before we step into Flutter directly, let’s see how it is currently in Android and iOS.

Android: Activity Lifecycle.

onCreate():

Called when the activity is first created. This is where you should do all of your normal static set up: create views, bind data to lists, etc. This method also provides you with a Bundle containing the activity’s previously frozen state, if there was one. Always followed by onStart().

onRestart():

Called after your activity has been stopped, prior to it being started again. Always followed by onStart()

onStart():

Called when the activity is becoming visible to the user. Followed by onResume() if the activity comes to the foreground.

onResume():

Called when the activity will start interacting with the user. At this point your activity is at the top of the activity stack, with user input going to it. Always followed by onPause().

onPause ():

Called as part of the activity lifecycle when an activity is going into the background, but has not (yet) been killed. The counterpart to onResume(). When activity B is launched in front of activity A, this callback will be invoked on A. B will not be created until A’s onPause() returns, so be sure to not do anything lengthy here.

onStop():

Called when you are no longer visible to the user. You will next receive either onRestart(), onDestroy(), or nothing, depending on later user activity. Note that this method may never be called, in low memory situations where the system does not have enough memory to keep your activity’s process running after its onPause() method is called.

onDestroy():

The final call you receive before your activity is destroyed. This can happen either because the activity is finishing (someone called finish() on it, or because the system is temporarily destroying this instance of the activity to save space. You can distinguish between> these two scenarios with the isFinishing() method.

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
public class MainActivity extends Activity {
    String tag = "LifeCycleEvents";
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.main);
       Log.d(tag, "In the onCreate() event");
    }
    public void onStart()
    {
       super.onStart();
       Log.d(tag, "In the onStart() event");
    }
    public void onRestart()
    {
       super.onRestart();
       Log.d(tag, "In the onRestart() event");
    }
    public void onResume()
    {
       super.onResume();
       Log.d(tag, "In the onResume() event");
    }
    public void onPause()
    {
       super.onPause();
       Log.d(tag, "In the onPause() event");
    }
    public void onStop()
    {
       super.onStop();
       Log.d(tag, "In the onStop() event");
    }
    public void onDestroy()
    {
       super.onDestroy();
       Log.d(tag, "In the onDestroy() event");
    }
}

Android: Fragment Lifecycle.

1) onAttach() =>

Called when the fragment has been associated with the activity (the Activity is passed in here).

2) onCreateView() =>

Called to create the view hierarchy associated with the fragment.

3) onActivityCreated() =>

Called when the activity’s onCreate() method has returned.

4) onDestroyView() =>

Called when the view hierarchy associated with the fragment is being removed.

5) onDetach()=>

Called when the fragment is being disassociated from the activity.

public class SomeFragment extends Fragment {
    ThingsAdapter adapter;
    FragmentActivity listener;
    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (context instanceof Activity){
            this.listener = (FragmentActivity) context;
        }
    }
      
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ArrayList things = new ArrayList();
        adapter = new ThingsAdapter(getActivity(), things);
    }



    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_some, parent, false);
    }
	



    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        ListView lv = (ListView) view.findViewById(R.id.lvSome);
        lv.setAdapter(adapter);
    }



    @Override
    public void onDetach() {
        super.onDetach();
        this.listener = null;
    }
        



    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
    }
}source: here

iOS: Lifecycle

Source: https://developer.apple.com/documentation/uikit/uiviewcontroller

func viewWillAppear(Bool)

Notifies the view controller that its view is about to be added to a view hierarchy.

func viewDidAppear(Bool)

Notifies the view controller that its view was added to a view hierarchy.

func viewWillDisappear(Bool)

Notifies the view controller that its view is about to be removed from a view hierarchy.

func viewDidDisappear(Bool)

Notifies the view controller that its view was removed from a view hierarchy.

var isBeingDismissed: Bool

A Boolean value indicating whether the view controller is being dismissed.

var isBeingPresented: Bool

A Boolean value indicating whether the view controller is being presented.

var isMovingFromParent: Bool

A Boolean value indicating whether the view controller is being removed from a parent view controller.

var isMovingToParent: Bool

A Boolean value indicating whether the view controller is being moved to a parent view controller.

Flutter:

There are two types of widgets in Flutter. They are

* Stateful widgets

* Stateless widgets

A Stateless widget is used when the values do not change and the stateful widget is used when values change.

For example, A Text view can display a string just like the label and it does not chnage the value dynamically. Each of the above mentioned widgets have a build method with a BuilContext that handles the location of the widget in the widget tree.

The StatelessWidget Lifecycle.

The Stateless widget does not change the value dynamically and is built on its own configuration. The build method of the StatelessWidget can be called from three different scenarios.

1. First time when the widget is created

2. Widget’s parent changes

3. Inherited widget has changed

In IDEA Intellij you can easliy create a Stateless widget using a template.

Create a new dart file. Then type “stless” and press enter.

A new Statless Widget is created for you with an empty Container. Provide a new name to the class.

* Dont forget to import “material.dart”

The Stateful Widget Lifecycle.

The Stateful widget can change the value dynamically and is built on its own configuration. A Text view displays a static text but based on the user’s input or action we can change the values of the Text view dynamically. This type of widget has mutable state that can change over time. This widget is declared with two classes, the Stateful widget class and the State class. The StatefulWidget class is rebuilt when the widget configutation changes but the state class can persist, enhancing performance.

In IDEA Intellij you can easily create a Stateful widget using a template. Create a new dart file and then type “stfull” and press enter as shown below

A new Stateful Widget is created for you with an empty Container. Provide a new name to the class. In this example I’ve given MyStatefulWidget.

import 'package:flutter/material.dart';

class MyStatefulWidget extends StatefulWidget {

  @override

  _MyStatefulWidgetState createState() => _MyStatefulWidgetState();

}

class _MyStatefulWidgetState extends State {

  int counter;

  @override

  void initState() {

    super.initState();

    print("initState");

    counter = 0;

  }

  @override

  Widget build(BuildContext context) {
    return Scaffold(

      appBar: AppBar(

        backgroundColor: Colors.amber,

        title: Text("Stateful widget"),

      ),

      body: SafeArea(

        child: Center(

          child: Container(

            child: Column(

              mainAxisAlignment: MainAxisAlignment.center,

              children: [

                Text("$counter time(s) the button was pressed"),

                MaterialButton(

                  child: Text("Click"),

                  onPressed: _incrementCounter,

                color: Colors.amber,

                textColor: Colors.black,)

              ],

            ),

          ),

        ),

      ),

    );

  }

  @override

  void dispose() {

    super.dispose();

    print("dispose");

  }

  @override

  void didChangeDependencies() {

    super.didChangeDependencies();

    print("didChangeDependencies");

  }

  @override

  void didUpdateWidget(covariant MyStatefulWidget oldWidget) {

    super.didUpdateWidget(oldWidget);

    print("didUpdateWidget");

  }

  @override

  void deactivate() {

    super.deactivate();

    print("deactivate");

  }

  void _incrementCounter(){

    setState(() {

      print("setState");

      counter++;

    });

  }

}

Logs:

flutter: initState
flutter: didChangeDependencies
flutter: didUpdateWidget
flutter: setState
flutter: setState
flutter: setState
flutter: setState 

initState()

Called when the object is inserted into the tree

@override
void initState() {
  super.initState();
  _clipboardStatus?.addListener(_onChangedClipboardStatus);
  widget.controller.addListener(_didChangeTextEditingValue);
  _focusAttachment = widget.focusNode.attach(context);
  widget.focusNode.addListener(_handleFocusChanged);
  _scrollController = widget.scrollController ?? ScrollController();
  _scrollController.addListener(() { _selectionOverlay?.updateForScroll(); });
  _cursorBlinkOpacityController = AnimationController(vsync: this, duration: _fadeDuration);
  _cursorBlinkOpacityController.addListener(_onCursorColorTick);
  _floatingCursorResetController = AnimationController(vsync: this);
  _floatingCursorResetController.addListener(_onFloatingCursorResetTick);
  _cursorVisibilityNotifier.value = widget.showCursor;
}

dispose()

Called when this object is removed from the tree permanently.

@override
void dispose() {
  _currentAutofillScope?.unregister(autofillId);
  widget.controller.removeListener(_didChangeTextEditingValue);
  _cursorBlinkOpacityController.removeListener(_onCursorColorTick);
  _floatingCursorResetController.removeListener(_onFloatingCursorResetTick);
  _closeInputConnectionIfNeeded();
  assert(!_hasInputConnection);
  _stopCursorTimer();
  assert(_cursorTimer == null);
  _selectionOverlay?.dispose();
  _selectionOverlay = null;
  _focusAttachment.detach();
  widget.focusNode.removeListener(_handleFocusChanged);
  WidgetsBinding.instance.removeObserver(this);
  _clipboardStatus?.removeListener(_onChangedClipboardStatus);
  _clipboardStatus?.dispose();
  super.dispose();
}

didChangeDependencies()

Called when a dependency of this State object changes.

@override
void didChangeDependencies() {
  super.didChangeDependencies();

  final AutofillGroupState newAutofillGroup = AutofillGroup.of(context);
  if (currentAutofillScope != newAutofillGroup) {
    _currentAutofillScope?.unregister(autofillId);
    _currentAutofillScope = newAutofillGroup;
    newAutofillGroup?.register(this);
    _isInAutofillContext = _isInAutofillContext || _shouldBeInAutofillContext;
  }

  if (!_didAutoFocus && widget.autofocus) {
    _didAutoFocus = true;
    SchedulerBinding.instance.addPostFrameCallback((_) {
      if (mounted) {
        FocusScope.of(context).autofocus(widget.focusNode);
      }
    });
  }
} 

didUpdateWidget()

Called whenever the widget configuration changes.

@override
void didUpdateWidget(EditableText oldWidget) {
  super.didUpdateWidget(oldWidget);
  if (widget.controller != oldWidget.controller) {
    oldWidget.controller.removeListener(_didChangeTextEditingValue);
    widget.controller.addListener(_didChangeTextEditingValue);
    _updateRemoteEditingValueIfNeeded();
  }
  if (widget.controller.selection != oldWidget.controller.selection) {
    _selectionOverlay?.update(_value);
  }
  _selectionOverlay?.handlesVisible = widget.showSelectionHandles;
  _isInAutofillContext = _isInAutofillContext || _shouldBeInAutofillContext;

  if (widget.focusNode != oldWidget.focusNode) {
    oldWidget.focusNode.removeListener(_handleFocusChanged);
    _focusAttachment?.detach();
    _focusAttachment = widget.focusNode.attach(context);
    widget.focusNode.addListener(_handleFocusChanged);
    updateKeepAlive();
  }
  if (!_shouldCreateInputConnection) {
    _closeInputConnectionIfNeeded();
  } else {
    if (oldWidget.readOnly && _hasFocus)
      _openInputConnection();
  }

  if (widget.style != oldWidget.style) {
    final TextStyle style = widget.style;
    // The _textInputConnection will pick up the new style when it attaches in
    // _openInputConnection.
    if (_textInputConnection != null && _textInputConnection.attached) {
      _textInputConnection.setStyle(
        fontFamily: style.fontFamily,
        fontSize: style.fontSize,
        fontWeight: style.fontWeight,
        textDirection: _textDirection,
        textAlign: widget.textAlign,
      );
    }
  }
  if (widget.selectionEnabled && pasteEnabled && widget.selectionControls?.canPaste(this) == true) {
    _clipboardStatus?.update();
  }
} 

deactivate()

Stop visually emphasizing this part of the material.

void deactivate() {
  _active = false;
  _alphaController.reverse();
}

setState()

Notify the framework that the internal state of this object has changed.

Future _incrementCounter() async {
  setState(() {
    _counter++;
  });
  Directory directory = await getApplicationDocumentsDirectory();
  final String dirName = directory.path;
  await File('$dir/counter.txt').writeAsString('$_counter');
}

Leave a Reply