This is the 10th day of my participation in the August More text Challenge. For details, see: August More Text Challenge

preface

In the last few articles we have written about the mechanism of state management and the plug-in for state management. In the next few articles we will use the official recommended Provider to modify the old code. You will see a big difference before and after the modification. As an example of Provider, I translated the shopping cart example article entitled “Introduction to Flutter and Practice (40)”.

The Provider profile

The latest version of Provider is 5.0.0. The component tree can share status data in the following ways:

Provider (
  create: (_) => Model(),
  child: someWidget(),
);
Copy the code

The Provider class itself does not automatically update its children when the state changes, so it is more common to use subclasses:

  • ListenableProvider: Listener implementedListenableAnd expose it to the child components. When an event is triggered, the child component is notified that the dependency has changed and the rebuild is implemented.
  • ChangeNotifierProviderOne of the most commonly used ways isListenableProviderThe subclass. Listening is implementedChangeNotifierInterface to an object when the object is callednotifyListeners, all listening components are notified to update the component.
  • ValueListenableProvider: Listener implementedValueListenableObject of the interface. When the object changes, its child components are updated.
  • StreamProvider: Listens on a Stream object and exposes its contents to a child component. Typically, a large amount of content, such as battery level monitoring, Firebase queries, and so on, is streamed to a component.

If an object is shared by multiple components, the following methods can be used:

// An object shared by multiple components
MyChangeNotifier variable;

ChangeNotifierProvider.value(
  value: variable,
  child: ...
)
Copy the code

There are three ways to use state data in widgets:

  • usecontext.read<T>()Method: This method returns a status data object of type T, but does not listen for changes to the object.
  • use context.watch<T>()Method: This method returns a t-type state data object and listens for changes to it, for situations that need to be updated based on status.
  • usecontext.select<T,R>(R cb(T value))Method: Returns an object of type R in a T objectWidgetListen only for part of the data on the status object.

For more details, please refer to the official documentation of Provider, which will be covered in the following chapters.

The code analysis

In the previous chapter, we introduced the management of a dynamic module, including the entire CRUD process. Details can be found in columns XII through XXVII, Introduction to Flutter and Combat. First, let’s change the code for the list. If we go back to the previous code, we will see why developers who use setState directly to update the interface are rated as “** **”.

Before the code is very messy, the first is in the list including the add, edit, delete the callback code, is to want to if the business is a bit more complex, is not the callback to full screen fly! The second is business code and UI code mixed, one is the code smelly and long – commonly known as 💩 code, the other is business code reuse reduced. For example, in other places, we may also use the dynamic business of adding, changing, deleting and checking, can not copy, paste again?

Code transformation

Now let’s use a Provider to separate the business from the UI. The business-related code is put into state management, and only the interface-related code is handled on the UI side. Dart = dynamic_model.dart = dynamic_model.dart = dynamic_model.dart

  • Tabular data: Use oneList<DynamicEntity>Object stores list data and defaults to an empty array.
  • Pagination data: Current page number_currentPage, fixed the page size to 20.
  • Refresh method:refresh, set the current page number to 1, and request the first page data again.
  • Loading method:load, add the current page number by 1, and request the data on page N.
  • Get paging data: Requests dynamic data based on the current page and page size, and updates the list data.
  • The reserveddelete,addupdateMethod for subsequent deletes, additions, and updates.

The code for the entire DynamicModel class is as follows. The key point here is to mix DynamicModel with ChangeNotifer’s features using the with ChangeNotifier. So that ChangeNotifierProvider can add listeners to it and notify state-dependent children of updates when notiferListeners are called.

class DynamicModel with ChangeNotifier {
  List<DynamicEntity> _dynamics = [];
  int _currentPage = 1;
  final int _pageSize = 20;

  List<DynamicEntity> get dynamics => _dynamics;

  void refresh() {
    _currentPage = 1;
    _requestNewItems();
  }

  void load() {
    _currentPage += 1;
    _requestNewItems();
  }

  void _requestNewItems() async {
    var response = await DynamicService.list(_currentPage, _pageSize);
    if(response ! =null && response.statusCode == 200) {
      List<dynamic> _jsonItems = response.data;
      List<DynamicEntity> _newItems =
          _jsonItems.map((json) => DynamicEntity.fromJson(json)).toList();
      if (_currentPage == 1) {
        _dynamics = _newItems;
      } else {
        _dynamics += _newItems;
      }
    }

    notifyListeners();
  }

  void removeWithId(String id) {}

  void add(DynamicEntity newDynamic) {}

  void update() {}
}

Copy the code

The next step is to use providers to provide state management for dynamic modules. As described in the previous chapters, providers need to be superior to components in order to provide state sharing for sub-components, so there are two ways to do this.

  • In the buildDynamicPageList pageapp.dartLt.DynamicPageAs aProviderAt a lower level. As shown below, the disadvantage of this approach is that this is the home page, and if the code of each module is directed to each other here, app.dart will become bloated and highly coupled.
@override
void initState() {
  super.initState();
  _homeWidgets = [
    ChangeNotifierProvider<DynamicModel>(
      create: (context) => DynamicModel(),
      child: DynamicPage(),
    ),
    MessagePage(),
    CategoryPage(),
    MineSliverPage(),
  ];
}
Copy the code
  • The use of aWidgetThe parcelDynamicPageAs well asProviderTo reduce the coupling of the code and avoidapp.dartThe code in
class DynamicWrapper extends StatelessWidget {
  const DynamicWrapper({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    returnChangeNotifierProvider( create: (context) => DynamicModel(), child: DynamicPage(), ); }}Copy the code

After that, DynamicPage was transformed. First, DynamicPage was changed from StatefulWidget to StatelessWidget, and then relevant business code was removed. Finally, get the data required by the interface from the Provider in the build method, or call the corresponding method. The modified DynamicPage is very clean, as shown below:

class DynamicPage extends StatelessWidget {
  DynamicPage({Key key}) : super(key: key);

  final EasyRefreshController _refreshController = EasyRefreshController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('dynamic', style: Theme.of(context).textTheme.headline4),
        actions: [
          IconButton(
              icon: Icon(Icons.add),
              onPressed: () {
                RouterManager.router
                    .navigateTo(context, RouterManager.dynamicAddPath);
              }),
        ],
        brightness: Brightness.dark,
      ),
      body: EasyRefresh(
        controller: _refreshController,
        firstRefresh: true,
        onRefresh: () async {
          context.read<DynamicModel>().refresh();
        },
        onLoad: () async {
          context.read<DynamicModel>().load();
        },
        child: ListView.builder(
          itemCount: context.watch<DynamicModel>().dynamics.length,
          itemBuilder: (context, index) {
            return DynamicItem(context.watch<DynamicModel>().dynamics[index],
                (Stringid) { context.read<DynamicModel>().removeWithId(id); }); },),),); }}Copy the code

In listView. builder we use the contxt.watch

method to get the latest dynamic list, so that we can refresh the interface when the list data changes. For calling methods, we use the context.read

method because we don’t need to listen for state changes. Run it and find the same effect as before, the transformation is complete.

Comparison before and after renovation

Let’s compare the DynamicPage code before and after modification, as shown in the figure below (the old code is on the left). As you can see, most of the code has been removed. In fact, the original code was 120 lines, but now the code is only 40 lines, a 2/3 reduction!

Of course, the code is reduced because the business code is taken away, but the business code itself is reusable. In the next article, we’ll take a look at how the Provider can further improve code reuse and simplify the page code after the deleters, additions, and edits are complete.

conclusion

The biggest benefit of Provider state management is the separation of UI layer and business layer code, which simplifies UI layer code and improves the reusability of business code. The Provider’s local refresh feature can also improve the performance of interface rendering.


I’m an island user with the same name on wechat. This is a column on introduction and practice of Flutter.

👍🏻 : feel a harvest please point to encourage!

🌟 : Collect articles, convenient to read back!

💬 : Comment exchange, mutual progress!