How to Use InheritedWidget in Flutter

Explores InheritedNotifier and InheritedModel - subclasses of InheritedWidget in Flutter
Jun 13 2023 · 6 min read

Background

InheritedWidget provides a way to share data across the widget tree in Flutter. It serves as a container for data that can be accessed by any descendant widget in the hierarchy. Whenever the data within the InheritedWidget changes, it triggers a rebuild of all the dependent widgets in the subtree.

However, besides InheritedWidget, there are two other subclasses, InheritedNotifier and InheritedModel, that offers unique approaches to sharing data in Flutter.

In this article, we will explore each of the subclasses and how they can be implemented by code.

Sponsored

Positive thinking leads to positive outcomes. Try out Justly, build good habits, and start thinking positively today!

What is InheritedNotifier?

What is InheritedNotifier?
InheritedNotifier is a subclass of InheritedWidget that combines the capabilities of InheritedWidget and ChangeNotifier. It propagates changes from a ChangeNotifier to descendant widgets. By wrapping ChangeNotifier with InheritedNotifier, you can automatically rebuild dependent widgets whenever the ChangeNotifier triggers updates using notifyListeners().

When creating an InheritedNotifier, you need to provide a Listenable object as its notifier parameter, such as ValueNotifier for simple objects or ChangeNotifier for complex objects.

To better understand the concept, let’s implement the demo.

We'll implement this Ui for better understanding of InheritedWidget.

Update State with Inherited Widget

Defining the Listenable Class

Create a class that is listenable to update the state.

class User {
  String? name;
  String? age;

User({this.name = '', this.age = ''});

}

class UserState with ChangeNotifier {

  User _user = User();
  User get user => _user;

  void setProfile(User updatedProfile) {
    _user = updatedProfile;
    notifyListeners();
  }  
}

Nothing fancy right??

By extending ChangeNotifier, the UserState class becomes a listenable class that can be used with InheritedNotifier to propagate state changes to its dependants in the widget tree.

So, let’s go ahead and use this class in InheritedNotifier class.

Creating InheritedNotifier Class

class UserStateNotifier extends InheritedNotifier<UserState> {
  const UserStateNotifier(
      {super.key, required UserState userState, required super.child})
      : super(notifier: userState);

  static User of(BuildContext context) {
    return context
        .dependOnInheritedWidgetOfExactType<UserStateNotifier>()!
        .notifier!
        .user;
  }

  @override
  bool updateShouldNotify(covariant InheritedNotifier<UserState> oldWidget) {
    return notifier!.user != oldWidget.notifier!.user;
  }
}
  • The of method is a static method in the UserStateNotifier class that allows us to retrieve the current user profile (User) from the nearest UserStateNotifier ancestor in the widget tree.
  • The updateShouldNotify method in the UserStateNotifier class is responsible for determining whether the dependents of the InheritedNotifier widget should be rebuilt or not. By implementing this method, we can optimize the rebuilding process by rebuilding only when necessary, reducing unnecessary rebuilds, and improving performance.

Let’s use it in the widget tree.

Update the state

We have a simple screen that displays the user’s name and age.

To update the user state, simply tap on the FAB button, which will open a modal bottom sheet with two Textfields, where user can update their information by adding text.

class HomePage extends StatelessWidget {
  HomePage({super.key});

  final TextEditingController _nameController = TextEditingController();
  final TextEditingController _ageController = TextEditingController();

  final UserState userState = UserState();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      ...
      floatingActionButton: FloatingActionButton.extended(
          onPressed: () {
            showModalBottomSheet(
                context: context,
                builder: (_) {
                  return Column(
                    children: [
                      TextField(
                        controller: _nameController,
                        decoration: const InputDecoration(
                            hintText: 'What is your name?',
                            border: OutlineInputBorder()),
                      ),
                      const SizedBox(height: 20,),
                      TextField(
                        controller: _ageController,
                        decoration: const InputDecoration(
                          hintText: 'What is your age?',
                          border: OutlineInputBorder(),
                        ),
                        keyboardType: TextInputType.number,
                      ),
                      ElevatedButton(
                          onPressed: () {
                            User user = User(
                              name: _nameController.text,
                              age: _ageController.text
                            );
                            userState.setProfile(user);
                            Navigator.pop(context);
                          },
                          child: const Text("Save"))
                    ],
                  );
                });
          },
          label: const Text('Set Profile')),
    );
  }
}

And you’re all set to go. Great job!! 👍

Using the InheritedNotifier

class HomePage extends StatelessWidget {
....
  final UserState userState = UserState();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('InheritedNotifier Demo'),
      ),
      body: Center(
        child: UserStateNotifier(
          userState: userState,
          child: Builder(builder: (context) {
            return Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text(
                  'Name : ${UserStateNotifier.of(context).name}',
                  style: const TextStyle(fontSize: 20),
                ),
                Text(
                  'Age : ${UserStateNotifier.of(context).age}',
                  style: const TextStyle(fontSize: 20),
                )
              ],
            );
          }),
        ),
      ),
      floatingActionButton: FloatingActionButton.extended(
    ....
  }
}

No explanation is needed!!

We can access the user information by calling UserStateNotifier.of(context), which returns the user information from the UserState class.

When you run the app, any change made through the modal bottom sheet will be reflected in real-time without the need for explicit state management calls.

Key Points

  • InheritedNotifier combines the functionalities of data sharing through InheritedWidget and state management through ChangeNotifier.
  • InheritedNotifier is designed to work with a single ChangeNotifier object.
  •  InheritedNotifier works well for small to medium-sized applications where you need a simple and efficient way to share and update states across multiple widgets.
  • Similar to InheritedWidget, InheritedNotifier provides global access to the shared state, which can make encapsulation and control challenging.

What is InheritedModel?

InheritedModel is another subclass of InheritedWidget that provides a more granular way of sharing data. With this, you can define multiple models within a single InheritedModel widget and select specific models for each descendant widget to depend on.

Let’s understand it by implementing it in the above example.

Creating the InheritedModel Class

Let’s define theInheritedModel class that we will name UserModel.

class UserModel extends InheritedModel<User> {
  final User? user;

  const UserModel({super.key, required super.child, this.user});

  static User? of(BuildContext context) {
    return InheritedModel.inheritFrom<UserModel>(context)!.user;
  }

  @override
  bool updateShouldNotify(UserModel oldWidget) {
    return user != oldWidget.user;
  }

  @override
  bool updateShouldNotifyDependent(
      UserModel oldWidget, Set<User> dependencies) {
    return user != oldWidget.user && dependencies.contains(user);
  }
}
  • InheritedModel has the static of and updateShouldNotify same as InheritedNotifier. The static of method takes BuildContext and uses the inheritFrom method provided by InheritedModel to retrieve the UserModel and access its user variable.
  • updateShouldNotifyDependent method is used to determine whether specific dependent widgets should be notified and rebuilt when the InheritedModel is rebuilt.
  • It takes two parameters: oldWidget which represents the previous instance of the model, and dependencies, which is a set of aspects that the widget depend on. Here, we can read the aspect value of the dependent widgets and can specify the condition on which we want our dependent widgets to be rebuilt.

Using the InheritedModel

Let’s use the InheritedModel in the UI.

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

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  final TextEditingController _nameController = TextEditingController();
  final TextEditingController _ageController = TextEditingController();

  User user = User();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('InheritedNotifier Demo'),
      ),
      body: Center(
        child: UserModel(
          user: user,
          child: Builder(builder: (context) {
            return Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text(
                  'Name : ${UserModel.of(context)!.name}',
                  style: const TextStyle(fontSize: 20),
                ),
                Text(
                  'Age : ${UserModel.of(context)!.age}',
                  style: const TextStyle(fontSize: 20),
                )
              ],
            );
          }),
        ),
      ),
      floatingActionButton: FloatingActionButton.extended(
          onPressed: () {
            showModalBottomSheet(
                context: context,
                builder: (_) {
                  return Column(
                    children: [
                      TextField(
                        controller: _nameController,
                        decoration: const InputDecoration(
                            hintText: 'What is your name?',
                            border: OutlineInputBorder()),
                      ),
                      const SizedBox(
                        height: 20,
                      ),
                      TextField(
                        controller: _ageController,
                        decoration: const InputDecoration(
                          hintText: 'What is your age?',
                          border: OutlineInputBorder(),
                        ),
                        keyboardType: TextInputType.number,
                      ),
                      ElevatedButton(
                          onPressed: () {
                            User updatedUSer = User(
                                name: _nameController.text,
                                age: _ageController.text);
                            Navigator.pop(context);
                            setState(() {
                              user = updatedUSer;
                            });
                          },
                          child: const Text("Save"))
                    ],
                  );
                });
          },
          label: const Text('Set Profile')),
    );
  }

Pretty similar, isn’t it?

And just like that, your app is ready. Hit that Run button and update your information to see the result.

How does it work?

When you click on the button, the value of the user variable changes. As this change happens, our UserModel observes the change and its updateShouldNotify method is called. This method checks if the dependent widgets need to be rebuilt. If this method returns true, which it would if any one of the properties changes, then updateShouldNotifyDependent is called.

Key Points

  • By specifying aspects, InheritedModel reduces the number of widget rebuilds that occur when changes are made to shared data. This optimization improves performance by only updating the widget that depends on specific changes.
  • InheriteModel allows you to manage multiple models and control specific aspects that each dependent widget relies on.
  • Compared to InheritedWidget, using InheritedModel requires more explicit specifications of dependencies and aspects, which can increase the complexity of the code.
  • You can use it when you have multiple models or shared data with different aspects that need to be managed separately and When you want to optimize performance by reducing unnecessary rebuilds of widgets that don’t depend on specific changes in the shared data

Conclusion

This is the most basic example I could come up with. In this article, we have explored InheritedNotifier and InheritedModel as subclasses of InheritedWidget in Flutter.

I hope this article has given you enough information to explore and utilize InheritedWidget in your Flutter projects, particularly for smaller projects with simpler use cases. If you have any questions or feedback, feel free to leave them in the comments section. I encourage you to try these classes and experiment with them in your own projects.

Thanks for reading!! 👋 👋

Useful Articles


sneha-s image
Sneha Sanghani
Flutter developer | Writing a Blog on Flutter development


sneha-s image
Sneha Sanghani
Flutter developer | Writing a Blog on Flutter development

footer
Subscribe Here!
Follow us on
2024 Canopas Software LLP. All rights reserved.