flutter data sharing series - Notes
Provider
InheritedWidget solves the problem of data sharing. It also brings the problem of unnecessary component update caused by data refresh. The Provider implements data sharing, data update, directional notification component update, etc. based on InheritedWidget.
Next, let's start with the use of Provider, gradually analyze the implementation of Provider and get familiar with the application of components.
Start with the official documents:
Create a new model Counter:
class Counter with ChangeNotifier { int _count = 0; int get count => _count; void increment() { _count++; notifyListeners(); } }
Initialize in place
Here we choose the main method:
void main() { runApp( MultiProvider( providers: [ ChangeNotifierProvider(create: (_) => Counter()), ], child: const MyApp(), ), ); }
Use and modify data
class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return const MaterialApp( home: MyHomePage(), ); } } class MyHomePage extends StatelessWidget { const MyHomePage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Example'), ), body: Center( child: Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: const <Widget>[ Text('You have pushed the button this many times:'), Extracted as a separate widget for performance optimization. As a separate widget, it will rebuild independently from [MyHomePage]. This is totally optional (and rarely needed). Similarly, we could also use [Consumer] or [Selector]. Count(), ], ), ), floatingActionButton: FloatingActionButton( key: const Key('increment_floatingActionButton'), Calls `context.read` instead of `context.watch` so that it does not rebuild when [Counter] changes. onPressed: () => context.read<Counter>().increment(), tooltip: 'Increment', child: const Icon(Icons.add), ), ); } } class Count extends StatelessWidget { const Count({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Text( Calls `context.watch` to make [Count] rebuild when [Counter] changes. '${context.watch<Counter>().count}', key: const Key('counterState'), style: Theme.of(context).textTheme.headline4); } }
Precautions for use
1. The shared data is defined as a private attribute, and the get method and update method are provided
This can effectively protect the data structure and uniformly modify the entry and acquisition methods.
2. Properly isolate the components that will be rebuilt. There are three common methods:
- Individually encapsulated components
- Package via Consumer
- Through the selector package, the selector can prevent rebuild when some values remain unchanged. The common place is to modify individual data in the list.
3. Distinguish between watch and read
watch and read are extension classes for BuildContext within the Provider framework. The user gets the data entry specified by the parent component. The difference is whether linsten is added, which is related to whether real-time refresh is required.
Simply distinguish between two scenarios:
- watch: the interface monitors data and updates the page
- read: respond to business interaction to operate and update data.
The source code of the two methods is also very simple, just to facilitate the generation of extension classes:
Exposes the [read] method. extension ReadContext on BuildContext { T read<T>() { return Provider.of<T>(this, listen: false); } } Exposes the [watch] method. extension WatchContext on BuildContext { T watch<T>() { return Provider.of<T>(this); } }
Here, let's take a look at the source code of Provider.of:
static T of<T>(BuildContext context, {bool listen = true}) { // Remove some unnecessary codes final inheritedElement = _inheritedElementOf<T>(context); if (listen) { context.dependOnInheritedElement(inheritedElement); } return inheritedElement.value; } static _InheritedProviderScopeElement<T> _inheritedElementOf<T>( BuildContext context, ) { // Remove some unnecessary codes _InheritedProviderScopeElement<T>? inheritedElement; if (context.widget is _InheritedProviderScope<T>) { context.visitAncestorElements((parent) { inheritedElement = parent.getElementForInheritedWidgetOfExactType< _InheritedProviderScope<T>>() as _InheritedProviderScopeElement<T>?; return false; }); } else { inheritedElement = context.getElementForInheritedWidgetOfExactType< _InheritedProviderScope<T>>() as _InheritedProviderScopeElement<T>?; } if (inheritedElement == null) { throw ProviderNotFoundException(T, context.widget.runtimeType); } return inheritedElement!; }
The difference between listening and not listening is that the way to get data is getelementforinheritedwidgetofexecttypeordependoninheritedelement. dependOnInheritedElement will add another registration, which will call the consumer's didChangeDependencies after data changes. For a little more specific analysis, you can see my previous articles——
[thinking about the use of InheritedWidget
](https://rzrobert.github.io/20...)
ChangeNotifier
A simple class that implements the Listenable interface. The official instructions are very simple:
A class that can be extended or mixed in that provides a change notification
Classes that can be extended or mixed to provide change notification
The algorithm complexity is O(1) to add listening and O(N) to remove listening. The efficient notification page for data update is refreshed. The data model of the provider must inherit it.
ChangeNotifierProvider
With the data model, we will start to create our ChangeNotifier, which requires the ChangeNotifierProvider.
Let's start with an error example, an error example, an error example, which is created in build through ChangeNotifierProvider.value:
ChangeNotifierProvider.value( value: new MyChangeNotifier(), child: ... )
This can cause memory leaks and potential bug s—— reference resources.
Of course, the existence of this method must have its meaning - if you already have a ChangeNotifier instance, you can construct it through ChangeNotifierProvider.value instead of create.
The correct method is to use the Create method to build:
ChangeNotifierProvider( create: (_) => new MyChangeNotifier(), child: ... )
Do not pass in variables to build the ChangeNotifier. In this way, when the variables are updated, the ChangeNotifier will not be updated.
int count; ChangeNotifierProvider( create: (_) => new MyChangeNotifier(count), child: ... )
If you really need to pass in variables, please use ChangeNotifierProxyProvider. The specific use of ChangeNotifierProxyProvider is not discussed here.