If you find state management too cumbersome like providers, Consumer is a high-performance, minimalist state management library for you.

Consumer is a react-Consumer approach to state management, using Dart’s Stream for publishing subscriptions.

A React project needs a state manager when it reaches a certain level. Flutter has several state management libraries, BLOC, Provider, Redux, and so on. However, their existing problem is that they do not provide a very convenient state management optimization scheme.

Consumer features just a published-subscription model plus a StateFulWidget, which has the advantage over existing state managers wrapped around inheritedWidgets in that it doesn’t require a top-level provider schema wrapper. Based on this, Consumer makes it easier for projects to create independent state management for sub-modules, and of course you can use consumer’s single mode to manage the state of the entire project.

Under this premise, we find that if the project is large enough, we need to split into multiple sub-state management, or some local state management, which can effectively reduce the impact range of event distribution and improve performance. Another feature of Consumer is that it forces consumers to describe the objects used for each subscription, so consumer can help optimize performance and block unnecessary updates.

Feature

  • Update only partial data changes
  • There is no need for a top-level Provider wrapped object
  • It is easy to set up independent state management for submodules
  • Very easy to use, with only 2 apis:setState,build

The API documentation:

  • Pub. Flutter – IO. Cn/packages/co…
  • Pub. Flutter – IO. Cn/documentati…

Install the consumer

Modify pubspec. Yaml:

dependencies:
  consumer: ^ 2.2.0
Copy the code

Or versions that support null-safety:

dependencies:
  consumer: ^ 2.3.0
Copy the code

Getting started guide

This is the default Flutter initialization project. We modified it using consumer to remove the StateFulWidget and replace it with the StatelessWidget:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

// *** defines a class that describes the state ***
class ExampleState {
  int counter = 0;
}

// *** 创建一个 consumer ***
var consumer = Consumer(ExampleState());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Consumer Demo Home Page')); }}class MyHomePage extends StatelessWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  _incrementCounter() {
    // *** uses setState to trigger the subscribed component to update ***
    consumer.setState((state) => state.counter++);
  }

  @override
  Widget build(BuildContext context) {
    print('The entire object is updated only once, and only subscribed components are updated when it is updated');

    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',),// *** subscribe to a component *** using consumer.build
            consumer.build((ctx, state) {
              return Text(
                state.counter.toString(),
                style: Theme.of(context).textTheme.display1,
              );
            }, memo:()=>[state.counter]),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment', child: Icon(Icons.add), ), ); }}Copy the code

parametermemoWhat is the function of?

As of v2.2.0, the Memo parameter is mandatory. This is because the authors believe that it is better to force developers to write performance-optimized code from the start than to wait until performance issues occur.

If your project has a lot of status subscriptions, using Memo can greatly improve performance.

The memo concept comes from react.Hooks, which describe objects that listen for changes. Updates are sent only when objects change.

As a rule, the Memo returns an array that defines the properties we need to use in the Builder object. Here are some examples:

If we create two widgets by consumer.build:

// *** definition a state ***
class ExampleState {
  List<String> animates = [];
  int age = 0;
  String name = 'dog';
}

// *** create a consumer ***
var consumer = Consumer(ExampleState());

Column(
  children: <Widget>[
    consumer.build((ctx, state) {
        print('Update when state.age change');
        return Text(
          '$state.age',
          style: Theme.of(context).textTheme.display1,
        );
      },
      memo: (state) => [state.age, state.animates],
    ),
    consumer.build((ctx, state) {
        print('Update when state.name change');
        return Text(
          state.name,
          style: Theme.of(context).textTheme.display1,
        );
      },
      memo: (state) => [state.name],
    ),
  ],
);
Copy the code

Then we update state.name:

consumer.setState((state){
  state.name = 'cat';
});
Copy the code

At this point, when we update state.name, only the widgets subscribed to memo: (state) => [state.name] will be updated, and all other widgets will be blocked by the consumer.

A complete example of using consumer and Memo to intercept updates

Using state management generally involves cross-component updates. Consumer recommends that you put states used by related components in a file and reference them in different components:

Lib /consumer.dart: Declare state and state consumers

import 'package:consumer/consumer.dart';

class ExampleState {
  int counter = 0;
  String time = DateTime.now().toString();
}

var consumer = Consumer(ExampleState());
Copy the code

Lib /main.dart: Using state consumers, draw components that need to be taken over by state

import 'package:flutter/material.dart';
import './consumer.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Material App',
      theme: ThemeData(primaryColor: Colors.blue),
      home: Scaffold(
        appBar: AppBar(
          title: Text("hello"),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text('counter:'),
              consumer.build(
                (ctx, state) {
                  print("update state.counter");
                  return Text(
                    state.counter.toString(),
                    style: Theme.of(ctx).textTheme.headline4,
                  );
                },
                memo: (state) => [state.counter],
              ),
              Container(
                child: TextButton(
                  onPressed: () {
                    consumer.setState((state) {
                      state.counter += 1;
                    });
                  },
                  child: Text("Only Change counter",
                      style: TextStyle(fontSize: 24)),
                ),
                margin: EdgeInsets.only(top: 20, bottom: 40),
              ),
              Text('time:'),
              consumer.build(
                (ctx, state) {
                  print("update state.time");
                  return Text(
                    state.time.toString(),
                    style: Theme.of(ctx).textTheme.headline4,
                  );
                },
                memo: (state) => [state.time],
              ),
              Container(
                child: TextButton(
                  onPressed: () {
                    consumer.setState((state) {
                      state.time = DateTime.now().toString();
                    });
                  },
                  child:
                      Text("Only Change time", style: TextStyle(fontSize: 24)),
                ),
                margin: EdgeInsets.only(top: 20),),),),),); }}Copy the code

Why did I use itconsumer.setStateThe Widget hasn’t been updated since?

Maybe you used state.name in the Builder, but the memo returns an array that does not contain state.name:

Center(
  child: consumer.build((ctx, state) {
      return Text(
        state.name,
        style: Theme.of(context).textTheme.display1,
      );
    },
    memo: (state) => [state.age],
  ),
);
Copy the code

Maybe your memo is not listening to anyone:

Center(
  child: consumer.build((ctx, state) {
      return Text(
        state.name,
        style: Theme.of(context).textTheme.display1,
      );
    },
    memo: (state) => [],
  ),
);
Copy the code

Maybe you just changed the object in the List or Map, but didn’t reset a new List or Map:

class ExampleState {
  List<String> names = ['dog'.'cat'];
}

var consumer = Consumer(ExampleState());

Center(
  child: consumer.build((ctx, state) {
      return Text(
        state.names[0],
        style: Theme.of(context).textTheme.display1,
      );
    },
    memo: (state) => [state.names],
  ),
);

// Error update:
Consumer.setState((state){
  state.names[0] = 'fish'
});

// Correct update:
Consumer.setState((state){
  List<String> names = [...state.names];
  names[0] = 'fish'
  state.names = names;
});
Copy the code

The State tip

If you need to do some calculations before updating, or if it’s easier to handle updates like arrays, you can create some function attributes for State:

Here is an example of modifying List data:

class ExampleState {
  int lastChangeNamesIndex;
  List<String> names = ['dog'.'cat'];

  changeNameAt(int index, String name) {
    lastChangeNamesIndex = index;
    List<String> nextNames = [...names]; nextNames[index] = name; names = nextNames; }}var consumer = Consumer(ExampleState());

Center(
  child: consumer.build((ctx, state) {
      return Text(
        state.names[state.lastChangeNamesIndex],
        style: Theme.of(context).textTheme.display1,
      );
    },
    memo: (state) => [state.names, state.lastChangeNamesIndex],
  ),
);

// Easily update names and lastChangeNamesIndex
consumer.setState((state){
  state.changeNameAt(0.'monkey');
})
Copy the code

That’s all

Thank you for reading this document and using Consumer.