We wrote about InheritedWidget in Practice and how to share and update data across widgets for those interested. With inheritedWidgets that are too cumbersome to use and inherently inappropriate for changing data, this article uses an InheritedWidget Provider that provides an elegant way to share and update data across widgets.

reference

  • Website sample
  • Official Documents (Chinese version)

You can Google the principle of Provider. This article presents several operation modes of Provider based on application scenarios. Demo address :gitee.com

  1. Read static data across widgets
  2. Variable data
  3. An array of
  4. Proxy and static data
  5. Proxy and mutable data

Read static data across widgets

Scene: There is a NormalModel object that shares data. It has four attributes: val1, val2, val3, val4. NormalWidget displays the data in NormalModel. Create a new child Widget to display the four attributes, Normal to display val1, Widget1 to display val2.. . A profile page that simulates real-world scenarios, such as your profile picture, nickname, and department, is displayed in a different Widget.

  1. Create an object to be shared
class NormalModel {
  String val1;
  String val2;
  String val3;
  String val4;

  NormalModel(this.val1, this.val2, this.val3, this.val4);
}
Copy the code
  1. usecontext.select()Use shared properties (code has been simplified; see the complete codedemo)
class Normal extends StatelessWidget { @override Widget build(BuildContext context) { var value = context.select<NormalModel, String>((p) => p.val1); . Column(children: [Text(value), Widget1()]), ... } } class Widget1 extends StatelessWidget { @override Widget build(BuildContext context) { var value = context.select<NormalModel, String>((p) => p.val2); . Column(children: [Text(value), Widget2()]), ... } } class Widget2 extends StatelessWidget { @override Widget build(BuildContext context) { var value = context.select<NormalModel, String>((p) => p.val3); . Column(children: [Text(value), Widget3()]), ... } } class Widget3 extends StatelessWidget { @override Widget build(BuildContext context) { var value = context.select<NormalModel, String>((p) => p.val4); . Text(value), ... }}Copy the code
  1. Routing showsNormalThe Widget,NormalSet to Providerchild, the use ofProvider.value()Create a static Provider.
Navigator.of(context).push(MaterialPageRoute(
                      builder: (context) => Provider.value(
                          value: NormalModel('Hello'.'China'.'hello'.'China'),
                          child: Normal())));
Copy the code

Variable data

Scenario: a counter example from a Flutter new project. Click on FloatActionButton Count to increment and display. Add data to a list, simulating a real-world scenario such as a shopping cart.

  1. The new classCountProviderinheritanceChangeNotifier, provide methodsplus()Every time,_countCall after changenotifyListeners()Inform that the data has been updated.
class CountProvider extends ChangeNotifier {
  int _count = 0;

  int get count => _count;

  voidplus() { _count++; notifyListeners(); }}Copy the code
  1. newChangeNotifyWidgetWidgets are used to display count and increment count.
  • usecontext.read<CountProvider>().plus()Increment count (read does not listen and is deprecated immediately).
  • usecontext.select<CountProvider, int>((p) => p.count)Read the count. Using select will listen for changes in count and is called when the count is updatedbuild()Method to refresh the interface.
class ChangeNotifyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('ChangeNotify')),
      body: Center(
        child: _CountLabel(),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => context.read<CountProvider>().plus(),
        tooltip: 'Increment', child: Icon(Icons.add), ), ); }}class _CountLabel extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var count = context.select<CountProvider, int>((p) => p.count);
    return Text('$count'); }}Copy the code
  1. Route display Widget.

Use the ChangeNotifierProvider to create a Provider that listens for changes. Create () returns the Provider and will use the Provider’s widget to set its child.

Navigator.of(context).push(MaterialPageRoute(
                      builder: (context) => ChangeNotifierProvider(
                          create: (_) => CountProvider(),
                          child: ChangeNotifyWidget())));
Copy the code

An array of

Scenario: ListView displays a dynamic list, click ➕ to add an element at the end, click the list row to touch the release data update, and long press the list row to delete that row. Simulate realistic scenarios such as shopping carts, including common operations such as adding to shopping carts, removing shopping carts, updating the number of items in shopping carts, and so on.

  1. Create new data for displayPerson, the core attribute is onlynameFor display in the list.
class Person {
  String name;
  Person(this.name);

  @override
  int get hashCode => name.hashCode;

  @override
  bool operator= = (Object other) {
    if (other.runtimeType == runtimeType && other is Person) {
      return other.name == name;
    }
    return false; }}Copy the code
  1. Create a ChangeNotifier to manage list data.
  • Provide list defaults.
  • Supports adding, deleting, updating, and obtaining methods.
class PersonProvider extends ChangeNotifier {
  PersonProvider() {
    for (var i = 0; i < 3; i++) {
      _items.add(Person('$i')); }}List<Person> _items = [Person('xxx'), Person('yyyy')];

  List<Person> get items => _items;

  void add(List<Person> items) {
    if (items is List&& items.isNotEmpty) { _items.addAll(items); notifyListeners(); }}void remove(int index) {
    if (items is List&& items.isNotEmpty) { _items.removeAt(index); notifyListeners(); }}void update(Person person, int index) {
    _items[index] = person;

    notifyListeners();
  }

  void updatePerson(Person person, String name) {
    _items.where((element) => element == person).map((e) => e.name = name);

    notifyListeners();
  }

  void updateName(String name, int index) {
    _items[index].name = name;

    notifyListeners();
  }

  Person getItem(int index) {
    if (index < 0 || index >= _items.length) return null;
    return_items[index]; }}Copy the code
  1. Create StatefullWidgetListProviderWidgetDisplay list data.
  • The list page Widget focuses on count, which triggers a list page refresh (new or removed) when the number changes. var count = context.select ((value) => value.items.length); ,>

  • List item _Cell, dynamically obtain the corresponding attribute of the data Model of the corresponding row through index. When the attribute value changes, the build() method of _Cell will be triggered: String name = context .select ((value) => value.getItem(index)? .name); ,>

  • New data: context.read ().add(..) ;

  • Context.read ().updatename (‘${Random().nextint (10000)}’, index);

  • Delete data :context.read ().remove(index)

class ListProviderWidget extends StatefulWidget {
  @override
  _ListProviderWidgetState createState() => _ListProviderWidgetState();
}

class _ListProviderWidgetState extends State<ListProviderWidget> {
  var random = Random();

  void _incrementCounter() {
    var max = 1000000;
    context.read<PersonProvider>().add([Person('${random.nextInt(max)}')]);
  }

  @override
  Widget build(BuildContext context) {
    print('build_ home page');
    var count =
        context.select<PersonProvider, int>((value) => value.items.length);
    return Scaffold(
      appBar: AppBar(
        title: Text('List ChangeNotifierProvider'),
      ),
      body: ListView.builder(
          itemCount: count,
          itemBuilder: (BuildContext context, int index) {
            return _Cell(index);
          }),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.); }}class _Cell extends StatelessWidget {
  final int index;

  _Cell(this.index);

  @override
  Widget build(BuildContext context) {
    print('build_ cell $index');
    String name = context
        .select<PersonProvider, String>((value) => value.getItem(index)? .name);if (name == null) return Container();

    return ListTile(
      title: Text(name),
      onTap: () {
        context
            .read<PersonProvider>()
            .updateName('${Random().nextInt(10000)}', index); }, onLongPress: () => context.read<PersonProvider>().remove(index), ); }}Copy the code

Proxy and static data

Scenario: New lists are generated based on different colors. Simulated real world scenario: The mailing list page updates data due to changes in the currently selected folder.

  1. Create the color proxy classProxyColor, updated when the color changes_itemsdata
class ProxyColor {
  List<String> _items = [];

  List<String> get items => _items;

  void update(String color) {
    List<String> result = [];
    for (var i = 0; i < 20; i++) {
      result.add('$color $i');
    }
    _items = result;
  }

  String getItemAt(int index) {
    return_items[index]; }}Copy the code
  1. Show color list
class ProxyProviderWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var count = context.select<ProxyColor, int>((p) => p.items.length);
    return Scaffold(
      appBar: AppBar(title: Text('ProxyProvider')), body: ListView.builder( itemCount: count, itemBuilder: (context, index) => _Cell(index)), ); }}class _Cell extends StatelessWidget {
  final int index;

  _Cell(this.index);

  @override
  Widget build(BuildContext context) {
    var value = context.select<ProxyColor, String>((p) => p.getItemAt(index));
    returnListTile(title: Text(value)); }}Copy the code
  1. useProxyProviderGenerate a new provider based on an existing provider
var colors = ['red'.'green'.'blue'];
var index = Random().nextInt(3);
Navigator.of(context).push(MaterialPageRoute(
    builder: (context) => MultiProvider(providers: [
          Provider.value(value: colors[index]),
          ProxyProvider<String, ProxyColor>(
              update: (context, color, proxyProvider) {
            var provider = ProxyColor();
            provider.update(color);
            return provider;
          })
        ], child: ProxyProviderWidget())));
Copy the code

Proxy and mutable data

Scenario: Get new providers based on different colors, support dynamic update of some other data.

  1. The newly built the Provider,_coloror_countTrigger on changenotifyListeners()Notifies external users of refreshing the page.
class ColorCountProvider extends ChangeNotifier { int _count = 0; String _color = 'black'; String get color => _color; set color(String color) { _color = color; notifyListeners(); } String get value => '$_color $_count'; void plus() { _count++; notifyListeners(); }}Copy the code
  1. The new Widget reads the count and color values and provides methods to increment the count value
class ChangeNotifyProxyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('ChangeNotifyProxyProvider')),
      body: Center(
        child: _CountLabel(),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => context.read<ColorCountProvider>().plus(),
        tooltip: 'Increment', child: Icon(Icons.add), ), ); }}class _CountLabel extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var count = context.select<ColorCountProvider, String>((p) => p.value);
    return Text('$count'); }}Copy the code
  1. Route open Widget, useChangeNotifierProxyProviderthecreateReturn the provider and use update to refresh the value of the current provider based on the previous provider.
var colors = ['red'.'green'.'blue'];
var index = Random().nextInt(3);
Navigator.of(context).push(MaterialPageRoute(
    builder: (context) => MultiProvider(providers: [
          Provider.value(value: colors[index]),
          ChangeNotifierProxyProvider<String,
                  ColorCountProvider>(
              create: (context) => ColorCountProvider(),
              update: (context, value, provider) {
                provider.color = value;
                return provider;
              })
        ], child: ChangeNotifyProxyWidget())));
Copy the code

conclusion

  1. Provider has three methods (normally only two).
methods instructions
read() Get the provider once, usually to call the provider’s methods.
select() Gets and listens for the required properties and fires the current widget’s when the properties are updatedbuildMethods.
watch() Not recommended. Assuming that notifyListener is triggered by multiple attributes, the build method is also triggered when the current irrelevant attributes are updated.
  1. Use providers to manage list data.
  • The widget that corresponds to the ListView uses select to get the count, so that when the count changes (delete, add), the Build method of the widget that corresponds to the ListView is triggered to refresh the entire list.
  • If the list item has child, the same is true with index. Do not pass models directly to the list item.
  1. Several Provider options
  • This is actually the most common oneChangeNotifierProxyProviderandMultiProvider, such as search, pass in the search criteria via provider.value (), and useChangeNotifierProxyProviderSearches based on search criteria and notifies outsiders to update the UI.
  • Provider. Value is used to share existing objects.
  • ProxyProvider and provider. value generate a new Provider.
  • ChangeNotifierProxyProvider just ProxyProvider varieties, new Provider support notifyListener ()
  1. The usage of the Provider is restricted. Error messages may be displayed if the Provider is used improperly during debugging. Note the error messages and adjust the usage mode.