Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding Flutter didChangeDependencies mechanics

Tags:

flutter

After reading the docs here and the State lifecycle here, I am still not sure about how didChangeDependencies works.

As far as I understand it will be triggered after initState and after any change in an InheritedWidget, but what are these changes? I think it's important to understand what changes trigger didChangeDependencies, so we can understand when and how to use it properly.

like image 234
Michel Feinstein Avatar asked Feb 12 '19 01:02

Michel Feinstein


People also ask

What is didChangeDependencies flutter?

According to the Flutter official docs, didChangeDependencies() is called when a dependency of the State object changes or immediately after initState(). It is safe to call BuildContext.

How does an inherited widget work?

InheritedWidget is a base class that allows classes that extend it to propagate information down the tree efficiently. Basically, it works by notifying registered build contexts if there is any change. Therefore, the descendant widgets that depend on it will only be rebuilt if necessary.

What is inherited widget in flutter?

In flutter, the inherited widget is a base class that allows those classes to extend the information under the tree from it. Inherited widgets are also a kind of state management technique. It works by telling registered build references when a change occurs.

What is build method in flutter?

The build method is called any time you call setState , your widget's dependencies update, or any of the parent widgets are rebuilt (when setState is called inside of those). Your widget will depend on any InheritedWidget you use, e.g. Theme. of(context) , MediaQuery. of(context) etc.


3 Answers

didChangeDependencies() Called when a dependency of this [State] object changes. So, exactly How it gets called? as by the above definition, it looks like it will be called after state changes but how we come to know the state is changed? Example: The below example uses the Provider state management mechanism to update the child widget from the parent widget. The Provider has an attribute called updateShouldNotify which decides whether to state is changed or not. If it's returning true then only didChangeDependencies get called in ChildWidget class. updateShouldNotify is returning true by default internally, as it knows the state got changed.

Why do we need updateShouldNotify?

It’s needed because if someone wants to update the state on a specific condition. Eg: if UI required to show only even values then we can add a condition like

updateShouldNotify: (oldValue, newValue) => newValue % 2 == 0,

Code Snippet:

class ParentWidget extends StatefulWidget {
  ParentWidget({Key key, this.title}) : super(key: key);
  final String title;
  @override
  _ParentWidgetState createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
  int _counter = 0;
  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Life Cycle'),
      ),
      body: Provider.value(
        value: _counter,
        updateShouldNotify: (oldValue, newValue) => true,
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text(
                'Press Fab button to increase counter:',
              ),
              ChildWidget()
            ],
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}
class ChildWidget extends StatefulWidget {
  @override
  _ChildWidgetState createState() => _ChildWidgetState();
}
class _ChildWidgetState extends State<ChildWidget> {
  int _counter = 0;
  @override
  void initState() {
    print('initState(), counter = $_counter');
    super.initState();
  }
  @override
  void didChangeDependencies() {
    _counter = Provider.of<int>(context);
    print('didChangeDependencies(), counter = $_counter');
    super.didChangeDependencies();
  }
  @override
  Widget build(BuildContext context) {
    print('build(), counter = $_counter');
    return Text(
      '$_counter',
    );
  }
}

Output Logs:

I/flutter ( 3779): didChangeDependencies(), counter = 1
I/flutter ( 3779): build(), counter = 1

For more info:

https://medium.com/@jitsm555/differentiate-between-didchangedependencies-and-initstate-f98a8ae43164

like image 53
Jitesh Mohite Avatar answered Oct 22 '22 12:10

Jitesh Mohite


TLDR

As the creator of the Widget, you set it's dependencies (InheritedWidget) by using of or maybeOf, etc., so you should understand when they call updateShouldNotify (or they're dependencies call it). It's quite complex:

Usage

You usually don't have to override this method in State because your widget will rebuild when a dependency changes anyway. If you want to do expensive work, like making a network request, then you would avoid making that network request in a normal build, and put this expensive work in didChangeDependencies instead. This allows you to only make these expensive operations when a dependency changes. Tbh, I struggled to think of a situation where you'd want to make an expensive operation purely if an InheritedWidget changes if you're properly structuring your app's logic into services. So I looked at the Flutter framework internals...

Examples of didChangeDependencies overrides:

  • Flutter's Image widget (the comments are my commentary, and tbh load is probably not the right word I'm using, but resolve isn't much better IMHO)
  @override
  void didChangeDependencies() {
    _updateInvertColors(); // Checks if the image should inverted or not.
    _resolveImage(); // Reloads the image if necessary?

    if (TickerMode.of(context)) // This method returns a bool: Whether tickers in the given subtree should be enabled or disabled.
      _listenToStream(); // Keep loading the image
    else
      _stopListeningToStream(keepStreamAlive: true); // Be efficient and not read from the stream if not needed. (ticker is false)

    super.didChangeDependencies();
  }
  • And when is didChangeDependencies called? aka. what are Image's dependencies (which are all InheritedWidgets)? It uses both of and maybeOf to register the InheritedWidget dependencies, including MediaQuery, Directionality, DefaultAssetBundle, TickerMode, Localizations. So when these dependencies change/ get updated, Image's didChangeDependencies will be called.

What is a dependency?

You make a widget depend on a subtype of InheritedWidget with InheritedWidgetType.of(context), which internally calls context.dependOnInheritedWidgetOfExactType<InheritedWidgetType>();. So the widget has a dependency on that InheritedWidget. For example, Theme.of(BuildContext context) can be seen here.

From the docs inside the Flutter Framework's comments about dependOnInheritedWidgetOfExactType:

Obtains the nearest widget of the given type T, which must be the type of a concrete [InheritedWidget] subclass, and registers this build context with that widget such that when that widget changes (or a new widget of that type is introduced, or the widget goes away), this build context is rebuilt so that it can obtain new values from that widget.

Once a widget registers a dependency on a particular type by calling this method, it will be rebuilt, and [State.didChangeDependencies] will be called, whenever changes occur relating to that widget until the next time the widget or one of its ancestors is moved (for example, because an ancestor is added or removed).

There's more docs, which are really interesting, have a read.


Jitesh's answer is currently just wrong. Dependencies are not Widget's state, they are only relevant for InheritedWidget.

like image 21
Ben Butterworth Avatar answered Oct 22 '22 12:10

Ben Butterworth


When Flutter calls updateShouldNotify() and it returns true, then widgets that requested an inherited widget in build() previously are notified by didChangeDependencies being called.

updateShouldNotify should return true if its state changed since the last time it was called.

like image 10
Günter Zöchbauer Avatar answered Oct 22 '22 12:10

Günter Zöchbauer