Managing State in Flutter

Sep 22 2022 · Dart 2.17, Flutter 3.0, Android Studio Chipmunk

Part 1: Understand State Management

07. Meet the Inherited Widget

Episode complete

Play next episode

Next
About this episode
Leave a rating/review
See forum comments
Cinema mode Mark complete Download course materials
Previous episode: 06. Extend a Value Notifier Next episode: 08. Mutate the Inherited Widget

Get immediate access to this and 4,000+ other videos and books.

Take your career further with a Kodeco Personal Plan. With unlimited access to over 40+ books and 4,000+ professional videos in a single subscription, it's simply the best investment you can make in your development career.

Learn more Already a subscriber? Sign in.

Transcript: 07. Meet the Inherited Widget

So you’ve learned about the various state management tools included with Flutter but I’d be remiss if I didn’t mention the biggest built included with Flutter: the inherited widget.

The Inherited Widget is the central mechanism that Flutter uses to manage app state. Like our previous state solution, the inherited widget is placed high in the widget tree. That way, child widgets can access it.

But unlike our previous attempts at state management, we don’t pass it down the widget tree using constructors. Rather, any widget underneath it can access from the build context.

In fact, if you’ve been using Flutter for any amount of time, you’ve already been using Inherited Widgets. Whenever you’ve fetched a Theme or used Media queries, those objects are inherited widgets.

When you create an inherited widget, a convention is to add the of static method. This allows you toe fetch the widget from anywhere in the widget tree. That is so long as you add it to the widget tree.

You should also override the updateShouldNotify. This method passes in an older version of the widget and returns a boolean value. This lets any child widgets know that they should be rebuilt.

By comparing the old widget with the current widget, we make that determination. If they are the same, then there’s no need for a rebuild. But if the old widget’s data is different, then you return true in order to rebuild the child tree. Let’s see this in action.

We’re going to update our model object to use our inherited widget. To get started, open your project in progress or download the starter project. Open up pillar.dart.

We’ll start by removing the value notifier from the class and reverting it back to it’s original state. Update it to the following:

class Pillar {
  var _articleCount = 0;
  int get articleCount => _articleCount;
  var active = true;
  final PillarType type;

  Pillar({required this.type, int articleCount = 10}) {
    _articleCount = articleCount;
  }

  void increaseArticleCount({int by = 1}) {
    _articleCount += by;
  }
}

This will cause some compile errors. Open main.dart. Remove the ValueListenableBuilder.

body: TutorialsPage(pillar: pillarData),

Now to create a place to hold our new state objects. Select the lib folder and create a folder called ‘state’. Then select the folder and create a new file. Call it pillar_widget.dart.

Like the other widgets, there’s actually a shortcut you can use to generate an inherited widget. We’ll do it by hand to walk through the code. First, import both the material library and our pillar model object.

import 'package:flutter/material.dart';
import '../models/pillar.dart';

Next, we’ll create the widget:

class PillarWidget extends InheritedWidget {

}

We’re getting an error because we haven’t implemented any of the requirements. We’ll start by adding our pillarData.

final Pillar pillarData;

Next, create a constructor for the widget.

const PillarWidget(
      {required this.pillarData, super.key, required super.child});

We have to pass in the key, and being that this widget sits on top of the widget tree, it takes a child widget. Next up is our of method. Add the following:

static PillarWidget of(BuildContext context) {
    final PillarWidget? result =
        context.dependOnInheritedWidgetOfExactType<PillarWidget>();
    assert(result != null, 'No PillarWidget found in context');
    return result!;
}

This is used so we can fetch the widget from lower in the widget tree. We call the method, dependOnInheritedWidgetOfExactType. We assume that the widget is in the tree. If it’s not, the app will crash. Finally we need to override the updateShouldNotify method. Add the following:

@override
bool updateShouldNotify(PillarWidget oldWidget) {
    return (oldWidget.pillarData.articleCount == pillarData.articleCount);
}

This let’s fluter know if the child subtree should be rebuilt. The method gets an older widget which we use to compare against the current one. Okay, now that we have our inherited widget in place, we need to use it. Open main.dart. First, import the widget.

import 'state/pillar_widget.dart';

Next, select the TutorialsPage widget and wrap it in a new widget. Change it to a PillarWidget, passing in the pillarData.

PillarWidget(
    pillarData: pillarData,
    child: TutorialsPage(),
),

We put the widget at the top of the widget tree. This means all of the sub-widgets can access the state. Open tutorial_page.dart. Import pillar_widget.dart.

import '../state/pillar_widget.dart';

We don’t need the pillarData anymore so update the StatefulWidget to remove it.

class TutorialsPage extends StatefulWidget {
  const TutorialsPage({ super.key});

  @override
  State<TutorialsPage> createState() => _TutorialsPageState();
}

Now we’ll update the state class to use the inherited widget. First, remove the pillar data from the TutorialWidget, adjust the const modifier.

const Center(
    child: TutorialWidget(),
),

Ignore the compile error. You’ll fix it in a moment. Next get a reference to the pillar data in the build method.

Widget build(BuildContext context) {
    final pillar = PillarWidget.of(context);

As you can see, it’s much easier accessing state now. Update the text to use the pillar reference.

'Total Tutorials: ${pillar.pillarData.articleCount}',

Okay, this is all set. Open tutorial_widget.dart. Update the widget to remove the pillar.

class TutorialWidget extends StatefulWidget {
  const TutorialWidget({super.key});

  @override
  State<TutorialWidget> createState() => _TutorialWidgetState();
}

Now to use our PillarWidget. Import the new widget.

import '../state/pillar_widget.dart';

We’ll get a reference to the pillar, and then update the code to use it.

Widget build(BuildContext context) {
    final pillar = PillarWidget.of(context);

Then update the Inkwell.

InkWell(
    onTap: () {
        pillar.pillarData.increaseArticleCount(by: 1);
    },
    child: Image.asset('assets/images/${pillar.pillarData.type.imageName}',
        width: 110, height: 110),
),

And then update the CircleAvatar:

child: CircleAvatar(
    backgroundColor: Colors.blue,
    child: Text(pillar.pillarData.articleCount.toString()),
),

And that’s it. Build and run. Tap on the button. That’s not exactly what we are looking for. Our model is being updated but our widget hasn’t changed. That’s because the inherited widget is immutable. In order to send state changes, you have rebuild the widget which you’ll do in the next episode.