An InheritedWidget is useful for lifting state up because it can be accessed by any widget in the widget tree.
InheritedWidgets are used to send information down the widget tree. To successfully use an InheritedWidget,
you can wrap it inside a StatefulWidget. The combination acts as a central storage from which widgets lower in the widget can access app state.
For the RWCourses app, the filterValue state for the CoursesPage and FilterPage will be lifted up in the widget tree, so that both pages can access the same state. In this episode, you’ll create the StatefulWidget and InheritedWidget combo called FilterStateContainer. In the next episode, you’ll let state up.
To get started, open your project in progress or download the sample project for this episode. Now run your app. First, create a new top level state folder.
Next, create a new file called filter_state_container.dart. We’re going to do things a little differently. Normally, we’d just have Visual Studio Code create our StatefulWidget template. In this case, we’re going to write it manually. You’ll see why in just a moment.
First, create a StatefulWidget called FilterStateContainer making sure to import the material library.
import 'package:flutter/material.dart';
class FilterStateContainer extends StatefulWidget {
}
Next create a widget property and then add a constructor.
final Widget child;
const FilterStateContainer({Key? key, required this.child}) : super(key: key);
Next, we’re going to override createState to return a filter state object. We’re getting a compile error because we haven’t made this class yet.
@override
State<StatefulWidget> createState() => FilterState();
The filter state is our internal state. You’ll notice that it’s a public. This is so we can access it in other parts of the app.
Let’s add a class stub for now.
class FilterState extends State<FilterStateContainer> {
@override
Widget build(BuildContext context) {
return Container();
}
}
Next, use the of pattern as a static method on the container to return the FilterState contained in the StatefulWidget
class FilterStateContainer extends StatefulWidget {
...
static FilterState of(BuildContext context) {
return context
.dependOnInheritedWidgetOfExactType<_FilterInheritedWidget>()!
.state;
}
We call dependOnInheritedWidgetOfExactType, which is typed by the _FilterInheritedWidget that we’ll create soon. Calling this method on the context means of method that we declared will return the closest inherited widget in the widget tree which returns a FilterState.
Now give FilterState a filterValue int property and a SharedPreferences property.
class FilterState extends State<FilterStateContainer> {
int filterValue = Constants.allFilter;
late SharedPreferences _prefs;
}
Make sure to import both Contents.dart and SharedPreferences.
import '../constants.dart';
Now create a loadValue method to read the filterValue from sharedPreferences, just like we did earlier on FilterPage and CoursesPage.
class FilterState extends State<FilterStateContainer> {
...
@override
void initState() {
super.initState();
_loadValue();
}
void _loadValue() {
SharedPreferences.getInstance().then((value) {
_prefs = value;
setState(() {
filterValue = _prefs.getInt(Constants.filterKey) as int;
});
});
}
Create an updateFilterValue that will later be used to update the saved filter value on the FilterPage.
class FilterState extends State<FilterStateContainer> {
...
void updateFilterValue(int value) {
setState(() {
_prefs.setInt(Constants.filterKey, value);
filterValue = value;
});
}
Ok, now it’s finally time to create the InheritedWidget class, a private class called _FilterInheritedWidget
class _FilterInheritedWidget extends InheritedWidget {
}
The inherited widget has a FilterState property, and also needs a constructor that populates the state and a child for the inherited widget.
class _FilterInheritedWidget extends InheritedWidget {
final FilterState state;
const _FilterInheritedWidget({
Key? key,
required this.state,
required Widget child,
}) : super(key: key, child: child);
We also need to override updateShouldNotify on the inherited widget
class _FilterInheritedWidget extends InheritedWidget {
...
@override
bool updateShouldNotify(_FilterInheritedWidget oldWidget) => true;
By returning true, we’re saying that all the widgets that depend on this inherited widget should rebuild when the state of the inherited widget changes.
Next we provide the necessary build method on filter state, which returns a _FilterInheritedWidget.
class FilterState extends State<FilterStateContainer> {
...
@override
Widget build(BuildContext context) {
return _FilterInheritedWidget(
state: this,
child: widget.child,
);
}
The state of the _FilterInheritedWidget is the current FilterState instance, and the child is the same child as that for the FilterStateContainer widget.
Ok that completes the FilterStateContainer and _FilterInheritedWidget. Next up, you’ll use them to lift up state in the app.