We can use an InheritedWidget to pass app state down the widget hierarchy, here we define a StateContainer to manage the state.
classStateContainerextendsStatefulWidget{ final AppState? state; final TodosRepository? repository; final Widget child; const StateContainer({super.key,required this.child,this.repository,this.state, }); static StateContainerState of(BuildContext context) {return context .dependOnInheritedWidgetOfExactType<_InheritedStateContainer>()! .data; } @override State<StateContainer> createState() => StateContainerState();}classStateContainerStateextendsState<StateContainer> { late AppState state; @override void initState() {if (widget.state != null) { state = widget.state!; } else { state = AppState.loading(); } widget.repository ?.loadTodos() .then((loadedTodos) { setState(() { state = AppState(todos: loadedTodos.map(Todo.fromEntity).toList()); }); }) .catchError((err) { setState(() { state.isLoading = false; }); });super.initState(); } void toggleAll() { setState(() { state.toggleAll(); }); } void clearCompleted() { setState(() { state.clearCompleted(); }); } void addTodo(Todo todo) { setState(() { state.todos.add(todo); }); } void removeTodo(Todo todo) { setState(() { state.todos.remove(todo); }); } void updateFilter(VisibilityFilter filter) { setState(() { state.activeFilter = filter; }); } void updateTodo( Todo todo, {bool? complete,String? id,String? note,String? task, }) { setState(() { todo.complete = complete ?? todo.complete; todo.id = id ?? todo.id; todo.note = note ?? todo.note; todo.task = task ?? todo.task; }); } @override void setState(VoidCallback fn) {super.setState(fn); widget.repository?.saveTodos( state.todos.map((todo) => todo.toEntity()).toList(), ); } @override Widget build(BuildContext context) {return _InheritedStateContainer(data: this, child: widget.child); }}InheritedWidget is the base class for widgets that efficiently propagate information down the tree.
class_InheritedStateContainerextendsInheritedWidget{ final StateContainerState data; const _InheritedStateContainer({required this.data, required super.child}); @override bool updateShouldNotify(_InheritedStateContainer old) => true;}typedef TodoUpdater =void Function( Todo todo, {bool complete,String id,String note,String task, });To obtain the nearest instance of a particular type of inherited widget from a build context, use BuildContext.dependOnInheritedWidgetOfExactType.
Inherited widgets, when referenced in this way, will cause the consumer to rebuild when the inherited widget itself changes state.
The convention is to provide two static methods, of and maybeOf, on the InheritedWidget which call BuildContext.dependOnInheritedWidgetOfExactType. This allows the class to define its own fallback logic in case there isn't a widget in scope.
• The ofmethod typically returns a non-nullable instance and asserts if theInheritedWidgetisn't found.• The maybeOfmethod returns a nullable instance, and returnsnullif theInheritedWidgetisn't found.
The of method is typically implemented by calling maybeOf internally.
The of and maybeOf methods return some data rather than the inherited widget itself; for example, in this case it could have returned a Color instead of the FrogColor widget.
The following is a skeleton of an inherited widget called FrogColor:
classFrogColorextendsInheritedWidget{ const FrogColor({super.key,required this.color,required super.child, }); final Color color; static FrogColor? maybeOf(BuildContext context) {return context.dependOnInheritedWidgetOfExactType<FrogColor>(); } static FrogColor of(BuildContext context) {final FrogColor? result = maybeOf(context);assert(result != null, 'No FrogColor found in context');return result!; } @override bool updateShouldNotify(FrogColor oldWidget) => color != oldWidget.color;}We can setup the Todo app with StateContainer:
Future<void> main() async { WidgetsFlutterBinding.ensureInitialized(); runApp( StateContainer( repository: LocalStorageRepository( localStorage: KeyValueStorage('inherited_widget_todos',await SharedPreferences.getInstance(), ), ), child: const InheritedWidgetApp(), ), );}We use InheritedWidgetApp to manage the MaterialApp with specific properties.
classInheritedWidgetAppextendsStatelessWidget{ const InheritedWidgetApp({super.key}); @override Widget build(BuildContext context) {return MaterialApp( theme: SampleTheme.lightTheme, darkTheme: SampleTheme.darkTheme, onGenerateTitle: (context) => InheritedWidgetLocalizations.of(context).appTitle, localizationsDelegates: [ SampleLocalizationsDelegate(), InheritedWidgetLocalizationsDelegate(), ], routes: { SampleRoutes.home: (context) => HomeScreen(), SampleRoutes.addTodo: (context) => AddEditScreen(), }, ); }}
夜雨聆风