flutter data sharing series - Notes

Keywords: Flutter

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.

Posted by jdog on Fri, 12 Nov 2021 09:28:40 -0800