Set Up InheritedWidget (AppState)

If you've used Flutter before, you've probably come across the of method on various classes:

  1. Theme.of(context).textTheme
  2. MediaQuery.of(context).size

Those widgets are inherited widgets that Flutter just happens to have built in. Because they're inherited, they havea special method called of, which you can use to access properties anywhere in it's Widget tree.

You can take advantage of this by writing your own Widgets that extend InheritedWidget. Once you have a workingInheritedWidget at the Root of your application, you can use an of method (that you'll write) to access it'sproperties anywhere in your app. In other words, it can be a central storage that holds your application state.

(For a more detailed explanation, read this blog post)

1. Setup AppState model

First, make a model class for your AppState. Add a new directory called models with a file called app_state.dart.

  1. lib/
  2. |-models/
  3. |- app_state.dart
  4. |-screens/
  5. |- home_screen.dart
  6. |- auth_screen.dart
  7. |-app.dart
  8. |-main.dart

This is a simple model class:

  1. class AppState {
  2. // Your app will use this to know when to display loading spinners.
  3. bool isLoading;
  4. // Constructor
  5. AppState({
  6. this.isLoading = false,
  7. this.user,
  8. });
  9. // A constructor for when the app is loading.
  10. factory AppState.loading() => new AppState(isLoading: true);
  11. @override
  12. String toString() {
  13. return 'AppState{isLoading: $isLoading, user: ${user?.displayName ?? 'null'}}';
  14. }
  15. }

2. Set Up the AppStateContainer (InheritedWidget)

The AppStateContainer is actually an InheritedWidget wrapped in a StatefulWidget. This basically makes thecontainer a stateful widget that has the ability to pass state all the way down the tree and be updated withsetState(), which would rerender all the ancestor widgets that rely on the slice of state updated. Cool.

First, add your app_state_container widget.

  1. lib/
  2. |-models/
  3. |- app_state.dart
  4. |-screens/
  5. |- home_screen.dart
  6. |- auth_screen.dart
  7. |- app.dart
  8. |- app_state_container.dart //new
  9. |- main.dart

In that file, you're going to have three classes:

  1. // The two normal StatefulWidget classes:
  2. class StateContainer extends StatefulWidget
  3. class StateContainerState extends State<StateContainer>
  4. // The InheritedWidget
  5. class _InheritedStateContainer extends InheritedWidget

You should start with this simple boiler plate:

  1. // app_state_container.dart
  2. import 'package:advanced_app/models/app_state.dart';
  3. import 'package:flutter/foundation.dart';
  4. import 'package:flutter/material.dart';
  5. class AppStateContainer extends StatefulWidget {
  6. // Your apps state is managed by the container
  7. final AppState state;
  8. // This widget is simply the root of the tree,
  9. // so it has to have a child!
  10. final Widget child;
  11. AppStateContainer({
  12. @required this.child,
  13. this.state,
  14. });
  15. // This creates a method on the AppState that's just like 'of'
  16. // On MediaQueries, Theme, etc
  17. // This is the secret to accessing your AppState all over your app
  18. static _AppStateContainerState of(BuildContext context) {
  19. return (context.inheritFromWidgetOfExactType(_InheritedStateContainer)
  20. as _InheritedStateContainer)
  21. .data;
  22. }
  23. @override
  24. _AppStateContainerState createState() => new _AppStateContainerState();
  25. }
  26. class _AppStateContainerState extends State<AppStateContainer> {
  27. // Just padding the state through so we don't have to
  28. // manipulate it with widget.state.
  29. AppState state;
  30. @override
  31. void initState() {
  32. // You'll almost certainly want to do some logic
  33. // in InitState of your AppStateContainer. In this example, we'll eventually
  34. // write the methods to check the local state
  35. // for existing users and all that.
  36. super.initState();
  37. }
  38. // So the WidgetTree is actually
  39. // AppStateContainer --> InheritedStateContainer --> The rest of your app.
  40. @override
  41. Widget build(BuildContext context) {
  42. return new _InheritedStateContainer(
  43. data: this,
  44. child: widget.child,
  45. );
  46. }
  47. }
  48. // This is likely all your InheritedWidget will ever need.
  49. class _InheritedStateContainer extends InheritedWidget {
  50. // The data is whatever this widget is passing down.
  51. final _AppStateContainerState data;
  52. // InheritedWidgets are always just wrappers.
  53. // So there has to be a child,
  54. // Although Flutter just knows to build the Widget thats passed to it
  55. // So you don't have have a build method or anything.
  56. _InheritedStateContainer({
  57. Key key,
  58. @required this.data,
  59. @required Widget child,
  60. }) : super(key: key, child: child);
  61. // This is a better way to do this, which you'll see later.
  62. // But basically, Flutter automatically calls this method when any data
  63. // in this widget is changed.
  64. // You can use this method to make sure that flutter actually should
  65. // repaint the tree, or do nothing.
  66. // It helps with performance.
  67. @override
  68. bool updateShouldNotify(_InheritedStateContainer old) => true;
  69. }

3. Wrap Your App with your Container

In your main.dart file:

  1. // main.dart
  2. import 'package:advanced_app/app.dart';
  3. import 'package:advanced_app/app_state_container.dart';
  4. import 'package:flutter/material.dart';
  5. void main() {
  6. // Wrap your App in your new storage container
  7. runApp(new AppStateContainer(
  8. child: new AppRootWidget(),
  9. ));
  10. }