1. Introduction
In the development of Flutter, the multi-state management of the page is more or less designed. If you are familiar with the Flutter technology, you should know some of the following state management frameworks, such as Bloc and Getx. I have used them. Overall It is still possible if the state management is not very complicated.
Next, let's take a look at how the state management framework Provider officially recommended by Flutter is used. Flutter provides a variety of different Providers for different types of objects; Provider also uses InheritWidget to put the shared state on top of the top-level MaterialApp;
- setState can refresh the widget subtree, the refresh range is too large, and the data object needs to be passed to the subclass.
- InheritedWidget does not need to pass a data object, and obtains the data of the parent class through
context.dependOnInheritedWidgetOfExactType<ShareDataWidget>();
However, the refresh range is large and can only be passed from top to bottom. - Provider can implement partial refresh. As long as the data object changes, the UI can automatically change, enabling responsive programming. Shield the refresh logic and realize the binding of responsive data and UI. Either the subclass or the superclass changes the data to refresh the bound UI.
2. Basic use
Before use, you need to add provider in pubspec.yaml
dependencies: flutter: sdk: flutter provider: ^6.0.2
2.1 Basic use of Provider
Provider is an observer mode based on data flow. The first step is to create a new data management class that inherits from ChangeNotifier. Next, let's take a look at how the official example is implemented using the Provider method.
import 'package:flutter/cupertino.dart'; class CountProviderModel extends ChangeNotifier { int _count = 0; int get count => _count; void increment() { print("increment"); _count++; notifyListeners(); } }
In the CountProviderModel class, we define a method for adding data, and finally call notifyListeners to send notifications. Next, we create a new test page, the root of the page is wrapped with the ChangeNotifierProvider component, and the place that needs to be refreshed is wrapped with the Consumer component for consumption
import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'count_provider.dart'; class ProviderPage extends StatelessWidget { const ProviderPage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return ChangeNotifierProvider<CountProviderModel>( create: (_) => CountProviderModel(), builder: (context, child) { return Scaffold( appBar: AppBar( title: const Text("ProviderPage"), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Text('You have pushed the button this many times:'), Consumer<CountProviderModel>( builder: (context, notifier, child) { return Text("${notifier.count}",style: const TextStyle( fontSize: 24, ), ); }, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: () => context.read<CountProviderModel>().increment(), tooltip: 'Increment', child: const Icon(Icons.add), ), ); }, ); } }
Finally, let's modify the entry main file of the Flutter project.
// 改写main.dart import 'package:flutter/material.dart'; import 'package:stateresearch/pages/ProviderPage.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter状态管理', theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: ProviderPage(), ); } }
Re-run the project, and the numbers will automatically increase when you click the button in the lower right corner.
2.2 Cross-page state sharing
As a global state management framework, state sharing across pages is necessary. For the convenience of description, we will create two new pages, ProviderPageTwo and ProviderPageThree. The corresponding codes are as follows:
ProviderPageTwo.dart
import 'package:flutter/material.dart'; import 'package:flutter_app/provider_three_page.dart'; import 'package:provider/provider.dart'; import 'count_provider.dart'; class ProviderPageTwo extends StatelessWidget { const ProviderPageTwo({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("ProviderPageTwo"), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Text('You have pushed the button this many times:'), Consumer<CountProviderModel>( builder: (context, notifier, child) { return Text("${notifier.count}",style: const TextStyle( fontSize: 24, )); }, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: () { context.read<CountProviderModel>().increment(); // 2秒后跳转至新的页面Future.delayed(const Duration(seconds: 2), () { Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context) { return ProviderPageThree(); })); }); }, tooltip: 'Increment', child: const Icon(Icons.add), ), ); } }
ProviderPageThree.dart
import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'count_provider.dart'; class ProviderPageThree extends StatelessWidget { const ProviderPageThree({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("ProviderPageThree"), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Text('You have pushed the button this many times:'), Consumer<CountProviderModel>( builder: (context, notifier, child) { return Text("${notifier.count}",style: const TextStyle( fontSize: 24, )); }, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: () => Provider.of<CountProviderModel>(context, listen: false).increment(), tooltip: 'Increment', child: const Icon(Icons.add), ), ); } }
Next, we perform global monitoring at the top level of the App, so other pages can get the Model without ChangeNotifierProvider.
void main() { runApp(ChangeNotifierProvider( create: (_) => CountProviderModel(), child: MyApp(), ),); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: ProviderPageTwo(), ); } }
2.3 Global sharing of multiple models
In addition to the previous two usage scenarios, Provider also supports multi-Model state sharing. First, we create a new ProviderModel class.
import 'package:flutter/material.dart'; class ListProviderModel extends ChangeNotifier { final List<String> _list = []; List<String> get list => _list; void push(String value) { _list.add(value); notifyListeners(); } }
Then, we modify the two pages ProviderPageTwo and ProviderPageThree, and the corresponding codes are as follows:
ProviderPageTwo.dart
import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_app/provider_three_page.dart'; import 'package:provider/provider.dart'; import 'count_provider.dart'; import 'list_provider.dart'; class ProviderPageTwo extends StatelessWidget { const ProviderPageTwo({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("ProviderPageTwo"), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Text('You have pushed the button this many times:'), Consumer<CountProviderModel>( builder: (context, notifier, child) { return Text("${notifier.count}"); }, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: () { context.read<CountProviderModel>().increment(); context.read<ListProviderModel>().push("List-${Random().nextInt(10)}"); Future.delayed(const Duration(seconds: 2), () { Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context) { return const ProviderPageThree(); })); }); }, tooltip: 'Increment', child: Icon(Icons.add), ), ); } }
ProviderPageThree.dart
import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'count_provider.dart'; import 'list_provider.dart'; class ProviderPageThree extends StatelessWidget { const ProviderPageThree({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("ProviderPageThree"), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Text('You have pushed the button this many times:'), Consumer<CountProviderModel>( builder: (context, notifier, child) { return Text("${notifier.count}"); }, ), Consumer<ListProviderModel>( builder: (context, notifier, child) { return Text("${notifier.list}"); }, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: () => Provider.of<CountProviderModel>(context, listen: false).increment(), tooltip: 'Increment', child: const Icon(Icons.add), ), ); } }
Finally, we modify the code of the main entry file and use MultiProvider to wrap multiple Models, as follows.
void main() { runApp( MultiProvider( providers: [ ChangeNotifierProvider(create: (_) => CountProviderModel()), ChangeNotifierProvider(create: (_) => ListProviderModel()), ], child: const MyApp(), ), ); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: ProviderPageTwo(), ); } }
3. Summary
As can be seen from the previous example, when using the Provider, we need to create a new Model object, which inherits from ChangeNotifier and is an observed object. When the Model object changes, we need to call notifyListeners to notify the observer to refresh. Here is the source code of ChangeNotifier:
class ChangeNotifier implements Listenable { ObserverList<VoidCallback>? _listeners = ObserverList<VoidCallback>(); @protected bool get hasListeners { return _listeners!.isNotEmpty; } @override void addListener(VoidCallback listener) { _listeners!.add(listener); } @override void removeListener(VoidCallback listener) { _listeners!.remove(listener); } @mustCallSuper void dispose() { _listeners = null; } @protected @visibleForTesting void notifyListeners() { if (_listeners != null) { final List<VoidCallback> localListeners = List<VoidCallback>.from(_listeners!); for (final VoidCallback listener in localListeners) { try { if (_listeners!.contains(listener)) listener(); } catch (exception, stack) { ...... } } } } }
ChangeNotifier adds the method to the array, and then notifies the observer implementation when calling notifyListeners. The whole workflow diagram is as follows.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。