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.
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.
We'll implement this Ui for better understanding of InheritedWidget
.
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.
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;
}
}
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.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.
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!! 👍
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.
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.InheritedWidget
, InheritedNotifier
provides global access to the shared state, which can make encapsulation and control challenging.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.
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.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.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.
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.InheritedWidget
, using InheritedModel
requires more explicit specifications of dependencies and aspects, which can increase the complexity of the code.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.
We’re Grateful to have you with us on this journey!
Suggestions and feedback are more than welcome!
Please reach us at Canopas Twitter handle @canopas_eng with your content or feedback. Your input enriches our content and fuels our motivation to create more valuable and informative articles for you.
Thanks for reading!! 👋 👋
Whether you need...