How to Use InheritedWidget in Flutter
Explores InheritedNotifier and InheritedModel - subclasses of InheritedWidget in Flutter
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?
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.
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 theUserStateNotifier
class that allows us to retrieve the current user profile (User
) from the nearestUserStateNotifier
ancestor in the widget tree. - The
updateShouldNotify
method in theUserStateNotifier
class is responsible for determining whether the dependents of theInheritedNotifier
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 throughInheritedWidget
and state management throughChangeNotifier
.InheritedNotifier
is designed to work with a singleChangeNotifier
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 staticof
andupdateShouldNotify
same asInheritedNotifier
. The staticof
method takesBuildContext
and uses theinheritFrom
method provided byInheritedModel
to retrieve theUserModel
and access itsuser
variable.updateShouldNotifyDependent
method is used to determine whether specific dependent widgets should be notified and rebuilt when theInheritedModel
is rebuilt.- It takes two parameters:
oldWidget
which represents the previous instance of the model, anddependencies
, 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
, usingInheritedModel
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 Related Articles
Thanks for the love you’re showing!
If you like what you read, be sure you won’t miss a chance to give 👏 👏👏 below — as a writer it means the world!
Feedbacks and suggestions are most welcome, add them in the comments section.
Follow Canopas to get updates on interesting articles!