In this post you will learn about maintaining app state with the help of Inherited Widget in Flutter.
Introduction
If you have ever worked on app using state management you must have had problem passing the state from root parent node down to leaf nodes (or child of child of child).
Consider a shopping app where you save all the products selected by user in a cart. You want to show this cart in your feed page, in products details page, may be in navigation bar … basically you want it to be available wherever you need.
Another use case can be that your app has multiple themes for daylight mode, dark mode, etc. You just want current mode chosen by user to be accessible in any widget you need.
Enter Inherited Widget In Flutter
Simply put, InheritedWidget helps to efficiently propagate any information down the tree. But there are some more things to consider before we begin:
- What information InheritedWidget will hold?
- What update in information should trigger listener widget rebuild?
- How to make information in InheritedWidget accessible to any child widget?
Now, lets take the Shopping Cart problem we described above and solve each step one by one.
1. Define what Information InheritedWidget will hold
// lib/shopping_cart.dart
class ShoppingCartInfo {
final List<int> productIds;
ShoppingCartInfo({this.productIds});
}
For this example we will only store id of products selected by user.
2. Define InheritedWidget and what information update should trigger listener widget rebuild
// lib/shopping_cart.dart
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
...
class ShoppingCart extends InheritedWidget {
const ShoppingCart({
Key key,
@required this.info,
});
final ShoppingCartInfo info;
@override
bool updateShouldNotify(ShoppingCart old) => !IterableEquality().equals(info.productIds, old.info.productIds);
}
Notice that any widget listening to InheritedWidget will only get notified when updateShouldNotify will return true. In this case, we compared productId list of old info with product list of current info.
If you just want to disable update, set it to always return false.
@override
bool updateShouldNotify(ShoppingCart old) => false;
3. Make info in InheritedWidget accessible to any child widget
The convention to access info InheritedWidget is by defining a static method in same class that will accept BuildContext from child widget and make call to BuildContext.inheritFromWidgetOfExactType.
// lib/shopping_cart.dart
...
class ShoppingCart extends InheritedWidget {
...
static ShoppingCartInfo of(BuildContext context) {
return (context.inheritFromWidgetOfExactType(ShoppingCart) as ShoppingCart).info;
}
...
}
Now that you have static function to access info from nearest parent of InheritedWidget type, you can easily access it from any child widget in the Widget Tree.
Example
// lib/products.dart
import 'package:flutter/material.dart';
class ProductFeed extends StatelessWidget {
@override
Widget build(BuildContext context) {
final info = ShoppingCart.of(context);
return Scaffold(
appBar: AppBar(title: Text('Product Feed')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('${info.productIds.length} selected'),
RaisedButton(
child: Text('Product A'),
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) => ProductDetail()),
);
},
),
]
),
),
);
}
}
class ProductDetail extends StatelessWidget {
@override
Widget build(BuildContext context) {
final info = ShoppingCart.of(context);
return Scaffold(
appBar: AppBar(title: Text('Retail Store')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Product A'),
Text('${info.productIds.length} selected'),
]
),
),
);
}
}
Notice that we did not pass any info to ProductDetail from ProductFeed, yet its so easy to access shopping cart info inside its build method.
Now its time to create and combine all the widgets we have written so far.
// lib/main.dart
import 'package:flutter/material.dart';
import 'shopping_cart.dart';
import 'products.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
final String appTitle = 'Shopping Cart Demo';
@override
Widget build(BuildContext context) {
return ShoppingCart(
info: ShoppingCartInfo(productIds: <int>[1, 2]),
child: MaterialApp(
title: appTitle,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: ProductFeed(),
),
);
}
}
Run this app then you will see shopping cart info in both Product Feed and Product Detail page. If you are new to navigation, you can head over to our tutorial for navigation in Flutter from here: Navigation and Routing
Also notice that we wrapped main MaterialApp inside ShoppingCart. This is crucial because every widget that needs to access info should be child of ShoppingCart widget.
Conclusion
Wrapping up: This is just basic way to use InheritedWidget. We only covered how to access information from the child widgets. Most often, you might need to update info in InheritedWidget and rebuild listening child.
For this, we recommend to head over our Flutter tutorial: