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:
DataInheritedWidgetextendsInheritedWidgetand holds adataproperty.- The constructor takes the data and a
child. updateShouldNotifychecks if the data has changed and returnstrueif a rebuild is required.- The static
ofmethod provides a convenient way to access theInheritedWidgetfrom 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:
MyScreenis a stateful widget that holds the data and a button to update it.DataInheritedWidgetwraps theCenterwidget and provides the current value ofdata.DataDisplayWidgetis a stateless widget that accesses the data usingDataInheritedWidget.of(context).data. When the data changes,DataDisplayWidgetis 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:
DataNotifierextendsChangeNotifierand holds the_dataproperty with a getter.- The
updateDatamethod 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:
DataInheritedNotifierextendsInheritedNotifier<DataNotifier>and holds aDataNotifier.- The constructor takes the
DataNotifierand achild. - The static
ofmethod provides a convenient way to access theDataNotifierfrom 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:
MyScreenis a stateful widget that holds aDataNotifierinstance.DataInheritedNotifierwraps theCenterwidget and provides thedataNotifier.DataDisplayWidgetis a stateless widget that accesses the data usingDataInheritedNotifier.of(context).data. When the data changes viadataNotifier.updateData(),DataDisplayWidgetis rebuilt to reflect the new value.
Comparing InheritedWidget and InheritedNotifier
InheritedWidget:- Requires manual implementation of
updateShouldNotifyto determine when to rebuild dependent widgets. - Useful for simple data propagation that doesn’t require frequent updates.
InheritedNotifier:- Automatically handles rebuilds using
Listenableobjects likeChangeNotifierorValueNotifier. - 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.