In fluent, state management is the top priority. Whenever we talk about this topic, there are always endless words.
After formally introducing the Provider, why do we need state management. If you already know this clearly, it is recommended to skip this section directly.
If our application is simple enough, as a declarative framework, you may only need to map data into views. You may not need state management, as shown below.
But with the increase of functions, your application will have dozens or even hundreds of states. At this time, your application should be like this.
What the hell is this. It's hard for us to clearly test and maintain our state, because it looks too complicated! Moreover, multiple pages will share the same state. For example, when you enter an article and exit to the external thumbnail display, the number of likes also needs to be displayed externally. At this time, you need to synchronize the two states.
In fact, from the beginning, fluent provided us with a state management method, that is, stateful widget. But we soon found that it was the culprit of the above reasons.
When a State belongs to a specific Widget and communicates among multiple widgets, although you can use callback to solve it, when the nesting is deep enough, we add a lot of terrible garbage code.
At this time, we urgently need an architecture to help us clarify these relationships, and the state management framework came into being.
What is a Provider
By using the Provider instead of writing the inheritedwidget manually, you will get the code to consult the allocation, delay loading, and reduce the number of hits each time you create a new class.
First, add in yaml. For the specific version number, refer to: Official Provider pub , the current version number is 4.1.3
Provider: ^4.1.3
Then run
flutter pub get
Get the latest package locally and import it in the required folder
import 'package:provider/provider.dart';
Simple example
We also use the click button to add an example of a number
First, create a Model that stores data
class ProviderModel extends ChangeNotifier { int _count=0; ProviderModel(); void plus() { /// Notify the listener to refresh when the data changes UI _count = _count + 1; notifyListeners(); } }
Construct view
/// use Consumer To monitor global refresh UI Consumer<ProviderModel>( builder: (BuildContext context, ProviderModel value, Widget child) { print('Consumer 0 Refresh'); _string += 'c0 '; return _Row( value: value._count.toString(), callback: () { context.read<ProviderModel>().plus(); }, ); }, child: _Row( value: '0', callback: () { context.read<ProviderModel>().plus(); }, ), )
Test to see the effect:
Multiple widgets of a single Model are refreshed separately (local refresh)
A single model can refresh multiple widgets on a single page. It is implemented by using selector < model, int >. First, let's look at the constructor:
class Selector<A, S> extends Selector0<S> { /// {@macro provider.selector} Selector({ Key key, @required ValueWidgetBuilder<S> builder, @required S Function(BuildContext, A) selector, ShouldRebuild<S> shouldRebuild, Widget child, }) : assert(selector != null), super( key: key, shouldRebuild: shouldRebuild, builder: builder, selector: (context) => selector(context, Provider.of(context)), child: child, ); }
Can see SelectorInheritedSelector0,Look againSelectorcruxbuildcode:
class _Selector0State<T> extends SingleChildState<Selector0<T>> { T value; Widget cache; Widget oldWidget; @override Widget buildWithChild(BuildContext context, Widget child) { final selected = widget.selector(context); var shouldInvalidateCache = oldWidget != widget || (widget._shouldRebuild != null && widget._shouldRebuild.call(value, selected)) || (widget._shouldRebuild == null && !const DeepCollectionEquality().equals(value, selected)); if (shouldInvalidateCache) { value = selected; oldWidget = widget; cache = widget.builder( context, selected, child, ); } return cache; } }
According to our incoming_ shouldRebuild to determine whether it needs to be updated. If it needs to be updated, execute the widget Build (context, selected, child), otherwise the cached cache is returned When not_ The shouldRebuild parameter is based on the widget The return value of selector (CTX) determines whether it is equal to the old value. If not, the UI is updated.
So it's OK for us not to write shouldRebuild.
Local refresh usage
Widget build(BuildContext context) { print('page 1'); _string += 'page '; return Scaffold( appBar: AppBar( title: Text('Provider Global and local refresh'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[ Text('Global refresh<Consumer>'), Consumer<ProviderModel>( builder: (BuildContext context, ProviderModel value, Widget child) { print('Consumer 0 Refresh'); _string += 'c0 '; return _Row( value: value._count.toString(), callback: () { context.read<ProviderModel>().plus(); }, ); }, child: _Row( value: '0', callback: () { context.read<ProviderModel>().plus(); }, ), ), SizedBox( height: 40, ), Text('Local refresh<Selector>'), Selector<ProviderModel, int>( builder: (ctx, value, child) { print('Selector 1 Refresh'); _string += 's1 '; return Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text('Selector<Model,int>Times:' + value.toString()), OutlineButton( onPressed: () { context.read<ProviderModel>().plus2(); }, child: Icon(Icons.add), ) ], ); }, selector: (ctx, model) => model._count2, shouldRebuild: (m1, m2) { print('s1: $m1 $m2 ${m1 != m2 ? 'Not equal, this refresh' : 'The data are equal, and will not be refreshed this time'}'); return m1 != m2; }, ), SizedBox( height: 40, ), Text('Local refresh<Selector>'), Selector<ProviderModel, int>( selector: (context, model) => model._count3, shouldRebuild: (m1, m2) { print('s2: $m1 $m2 ${m1 != m2 ? 'Not equal, this refresh' : 'The data are equal, and will not be refreshed this time'}'); return m1 != m2; }, builder: (ctx, value, child) { print('selector 2 Refresh'); _string += 's2 '; return Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text('Selector<Model,int>Times:' + value.toString()), OutlineButton( onPressed: () { ctx.read<ProviderModel>().plus3(); }, child: Icon(Icons.add), ) ], ); }, ), SizedBox( height: 40, ), Text('Refresh times and order:↓'), Text(_string), Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ OutlineButton( child: Icon(Icons.refresh), onPressed: () { setState(() { _string += '\n'; }); }, ), OutlineButton( child: Icon(Icons.close), onPressed: () { setState(() { _string = ''; }); }, ) ], ) ], ), ), ); }
effect:
When we click local refresh s1 and execute the build of s1, s1 is not equal and s2 is not equal. Output:
flutter: s2: 5 5 The data are equal, and will not be refreshed this time flutter: s1: 6 7 Equal, refresh this time flutter: Selector 1 Refresh flutter: Consumer 0 Refresh
When you click S2, the values of S2 are not equal, refresh the UI, and the data of S1 are equal, do not refresh the UI
flutter: s2: 2 3 Not equal, this refresh flutter: selector 2 Refresh flutter: s1: 0 0 The data are equal, and will not be refreshed this time flutter: Consumer 0 Refresh
You can see that the above two consumers refresh each time. Let's explore the reason.
Consumer global refresh
The Consumer inherits the SingleCHildStatelessWidget. When we call notification in the ViewModel, the current widget will be marked as dirty, and then execute the passed builder function in the build. In the next frame, the UI will be refreshed.
While selector < T, s > is executed when it is marked dirty_ The buildWithChild(ctx,child) function in Selector0State_ shouldRebuild to determine whether the widget needs to be executed Builder (CTX, selected, child)
Other uses
Multi model writing method
It only needs to be wrapped in all the superior packages that need models. When we need two models for a page, we usually write as follows:
class BaseProviderRoute extends StatelessWidget { BaseProviderRoute({Key key}) : super(key: key); @override Widget build(BuildContext context) { return MultiProvider( providers: [ ChangeNotifierProvider<ProviderModel>( create: (_) => ProviderModel(), ), ChangeNotifierProvider<ProviderModel2>(create: (_) => ProviderModel2()), ], child: BaseProvider(), ); } }
Of course, it is consistent with a single model.
Selector<ProviderModel2, int>( selector: (context, model) => model.value, builder: (ctx, value, child) { print('model2 s1 Refresh'); _string += 'm2s1 '; return Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text('Selector<Model2,int>Times:' + value.toString()), OutlineButton( onPressed: () { ctx.read<ProviderModel2>().add(2); }, child: Icon(Icons.add), ) ], ); }, ),
watch && read
The source code of watch is provider Of < T > (this), default provider Of < T > (this) listen=true
static T of<T>(BuildContext context, {bool listen = true}){ final inheritedElement = _inheritedElementOf<T>(context); if (listen) { context.dependOnInheritedElement(inheritedElement); } return inheritedElement.value; }
The read source code is provider Of < T > (this, listen: false), watch / read is simply written and has no advanced structure.
When we want to monitor the change of value, we use watch. When we want to call the function of model, we use read
reference resources
- Code warehouse: https://github.com/ifgyong/flutter-example
- Official Provider: https://github.com/rrousselGit/provider