Flutter is known for its powerful and flexible approach to state management. Among the many tools Flutter provides, InheritedWidget
and InheritedNotifier
are particularly useful for propagating data down the widget tree in an efficient and reactive manner. Understanding and utilizing these tools can greatly improve your application’s architecture and maintainability.
What are InheritedWidget
and InheritedNotifier
?
InheritedWidget
InheritedWidget
is a fundamental class in Flutter that allows widgets to efficiently propagate information down the widget tree. It enables descendant widgets to access data stored in an ancestor widget without the need for explicitly passing it through constructors.
InheritedNotifier
InheritedNotifier
is a specialized version of InheritedWidget
that combines the capabilities of an InheritedWidget
with a Listenable
object (such as a ValueNotifier
or ChangeNotifier
). This allows widgets to not only access data but also react to changes in that data, triggering rebuilds of dependent widgets when the data changes.
Why Use InheritedWidget
and InheritedNotifier
?
- Centralized Data: Provides a centralized place to store and manage data.
- Efficient Propagation: Propagates data efficiently through the widget tree.
- Reactivity: Allows widgets to react to changes in the data.
- Reduced Boilerplate: Reduces the amount of boilerplate code required to pass data between widgets.
- Improved Performance: Optimizes widget rebuilds by only rebuilding widgets that depend on the changed data.
How to Implement InheritedWidget
Step 1: Create an InheritedWidget
First, create a class that extends InheritedWidget
. You’ll need to override the updateShouldNotify
method, which determines whether dependent widgets should rebuild when the data changes.
import 'package:flutter/material.dart';
class DataInheritedWidget extends InheritedWidget {
final String data;
const DataInheritedWidget({
Key? key,
required this.data,
required Widget child,
}) : super(key: key, child: child);
@override
bool updateShouldNotify(DataInheritedWidget oldWidget) {
return oldWidget.data != data;
}
static DataInheritedWidget of(BuildContext context) {
final DataInheritedWidget? result = context.dependOnInheritedWidgetOfExactType();
assert(result != null, 'No DataInheritedWidget found in context');
return result!;
}
}
Explanation:
DataInheritedWidget
extendsInheritedWidget
and holds adata
property.- The constructor takes the data and a
child
. updateShouldNotify
checks if the data has changed and returnstrue
if a rebuild is required.- The static
of
method provides a convenient way to access theInheritedWidget
from descendant widgets usingcontext.dependOnInheritedWidgetOfExactType
.
Step 2: Use the InheritedWidget
Wrap a portion of your widget tree with the InheritedWidget
. Any descendant widgets can then access the data.
import 'package:flutter/material.dart';
class MyScreen extends StatefulWidget {
const MyScreen({Key? key}) : super(key: key);
@override
_MyScreenState createState() => _MyScreenState();
}
class _MyScreenState extends State<MyScreen> {
String data = "Initial Data";
void updateData(String newData) {
setState(() {
data = newData;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('InheritedWidget Example'),
),
body: DataInheritedWidget(
data: data,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
DataDisplayWidget(),
ElevatedButton(
onPressed: () {
updateData("Updated Data");
},
child: const Text('Update Data'),
),
],
),
),
),
);
}
}
class DataDisplayWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final inheritedWidgetData = DataInheritedWidget.of(context).data;
return Text('Data: $inheritedWidgetData');
}
}
In this example:
MyScreen
is a stateful widget that holds the data and a button to update it.DataInheritedWidget
wraps theCenter
widget and provides the current value ofdata
.DataDisplayWidget
is a stateless widget that accesses the data usingDataInheritedWidget.of(context).data
. When the data changes,DataDisplayWidget
is rebuilt to reflect the new value.
How to Implement InheritedNotifier
Step 1: Create a ChangeNotifier
First, create a class that extends ChangeNotifier
to hold the data that you want to propagate and notify listeners about.
import 'package:flutter/foundation.dart';
class DataNotifier extends ChangeNotifier {
String _data = "Initial Data";
String get data => _data;
void updateData(String newData) {
_data = newData;
notifyListeners();
}
}
Explanation:
DataNotifier
extendsChangeNotifier
and holds the_data
property with a getter.- The
updateData
method updates the data and callsnotifyListeners()
, which rebuilds all widgets that depend on thisChangeNotifier
.
Step 2: Create an InheritedNotifier
Create an InheritedWidget
that uses the ChangeNotifier
to provide data and notify listeners.
import 'package:flutter/material.dart';
class DataInheritedNotifier extends InheritedNotifier {
const DataInheritedNotifier({
Key? key,
required DataNotifier dataNotifier,
required Widget child,
}) : super(key: key, notifier: dataNotifier, child: child);
static DataNotifier of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType()!.notifier!;
}
}
Explanation:
DataInheritedNotifier
extendsInheritedNotifier<DataNotifier>
and holds aDataNotifier
.- The constructor takes the
DataNotifier
and achild
. - The static
of
method provides a convenient way to access theDataNotifier
from descendant widgets.
Step 3: Use the InheritedNotifier
Wrap a portion of your widget tree with the InheritedNotifier
. Any descendant widgets can then access the data and react to changes.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class MyScreen extends StatefulWidget {
const MyScreen({Key? key}) : super(key: key);
@override
_MyScreenState createState() => _MyScreenState();
}
class _MyScreenState extends State<MyScreen> {
final dataNotifier = DataNotifier();
@override
void dispose() {
dataNotifier.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('InheritedNotifier Example'),
),
body: DataInheritedNotifier(
dataNotifier: dataNotifier,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
DataDisplayWidget(),
ElevatedButton(
onPressed: () {
dataNotifier.updateData("Updated Data");
},
child: const Text('Update Data'),
),
],
),
),
),
);
}
}
class DataDisplayWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final dataNotifier = DataInheritedNotifier.of(context);
return Text('Data: ${dataNotifier.data}');
}
}
In this example:
MyScreen
is a stateful widget that holds aDataNotifier
instance.DataInheritedNotifier
wraps theCenter
widget and provides thedataNotifier
.DataDisplayWidget
is a stateless widget that accesses the data usingDataInheritedNotifier.of(context).data
. When the data changes viadataNotifier.updateData()
,DataDisplayWidget
is rebuilt to reflect the new value.
Comparing InheritedWidget
and InheritedNotifier
InheritedWidget
:- Requires manual implementation of
updateShouldNotify
to determine when to rebuild dependent widgets. - Useful for simple data propagation that doesn’t require frequent updates.
InheritedNotifier
:- Automatically handles rebuilds using
Listenable
objects likeChangeNotifier
orValueNotifier
. - Suitable for reactive data that changes frequently and requires immediate UI updates.
Conclusion
InheritedWidget
and InheritedNotifier
are essential tools for efficient state management in Flutter. While InheritedWidget
provides a basic mechanism for propagating data down the widget tree, InheritedNotifier
extends this with reactive capabilities, allowing widgets to react to data changes automatically. Understanding and using these tools can lead to more maintainable, performant, and reactive Flutter applications.