Article series

Status Management of Flutter Provider — Introduction, Class diagram analysis, basic usage

Flutter Provider State Management – Analysis of the use of eight providers

Video series

Status Management of Flutter Provider — Introduction, Class diagram analysis, basic usage

Flutter Provider State Management – Analysis of the use of eight providers

Source repository address

Github repository address

preface

In our last article, we introduced Provider and explained the class structure, and finally wrote a simple example. Through the last chapter, we have a basic understanding of Provider. In this chapter, we will talk about the eight providers of Provider and their differences in use.

Provider

A Provider is the most basic Provider component that can be used to provide a value anywhere in the component tree, but it does not update the UI when the value changes, as shown in the following example

Step 1: Create the model

class UserModel {

  String name = "Jimi";

  void changeName() {
    name = "hello"; }}Copy the code

Step 2: Application entry setup

return Provider<UserModel>(
  create: (_) => UserModel(),
  child: MaterialApp(
    debugShowCheckedModeBanner: false,
    home: ProviderExample(),
  ),
);
Copy the code

Step 3: Use shared data

As for Consumer, we only need to know that there are two consumers. The first one is used to show the data of the model, and the second one is used to change the data of the model.

  • The first oneComsumerIs the data used to read the modelname
  • The secondConsumerData used to change the modelname
import 'package:flutter/material.dart';
import 'package:flutter_provider_example/provider_example/user_model.dart';
import 'package:provider/provider.dart';

class ProviderExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("ProviderExample"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Consumer<UserModel>(
              builder: (_, userModel, child) {
                return Text(userModel.name,
                    style: TextStyle(
                        color: Colors.red,
                        fontSize: 30)); }, ), Consumer<UserModel>( builder: (_, userModel, child) {return Padding(
                  padding: EdgeInsets.all(20),
                  child: ElevatedButton(
                    onPressed: (){
                      userModel.changeName();
                    },
                    child: Text("Change value"),),); },),],),),); }}Copy the code

The results

When we click the button, the model data changes, but the UI does not change or rebuild after the model data changes, because the Provider Provider component does not listen for changes in the values it provides.

ChangeNotifierProvider

Unlike the Provider component, the ChangeNotifierProvider listens for changes to the model object and reconstructs the Consumer when the data changes, as shown in the following example

Step 1: Create the model

More carefully, we can see that there are two changes to the model defined here, as follows:

  • With theChangeNotifier
  • Call thenotifyListeners()

Because the model class uses the ChangeNotifier, we can access the notifyListeners() and whenever it is called, the ChangeNotifierProvider is notified and the consumer recreates the UI.

import 'package:flutter/material.dart';

class UserModel1 with ChangeNotifier {

  String name = "Jimi";

  void changeName() {
    name = "hello"; notifyListeners(); }}Copy the code

Step 2: Application entry setup

return ChangeNotifierProvider<UserModel1>(
  create: (_) => UserModel1(),
  child: MaterialApp(
    debugShowCheckedModeBanner: false,
    home: ChangeNotifierProviderExample(),
  ),
);
Copy the code

Step 3: Use shared data

import 'package:flutter/material.dart';
import 'package:flutter_provider_example/change_notifier_provider_example/user_model1.dart';
import 'package:provider/provider.dart';

class ChangeNotifierProviderExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("ChangeNotifierProvider"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Consumer<UserModel1>(
              builder: (_, userModel, child) {
                return Text(userModel.name,
                    style: TextStyle(
                        color: Colors.red,
                        fontSize: 30)); }, ), Consumer<UserModel1>( builder: (_, userModel, child) {return Padding(
                  padding: EdgeInsets.all(20),
                  child: ElevatedButton(
                    onPressed: (){
                      userModel.changeName();
                    },
                    child: Text("Change value"),),); },),],),),); }}Copy the code

The results

FutureProvider

In simple terms, FutureProvider is used to provide values that may not be ready when they are ready to be used in the component tree, mainly to ensure that null values are not passed to any child components, and that the FutureProvider has an initial value that the child components can use and tell the child components to rebuild with the new value.

Note:

  • FutureProviderIt will only be rebuilt once
  • The initial value is displayed by default
  • Then displayedFuturevalue
  • It will not be rebuilt again

Step 1: Create the model

The difference here is that the constructor is added, and changeName is changed to a Future. We simulate a network request delay of two seconds before changing its value.

class UserModel2{

  UserModel2({this.name});

  String? name = "Jimi";

  Future<void> changeName() async {
    await Future.delayed(Duration(milliseconds: 2000));
    name = "hello"; }}Copy the code

Step 2: Provide a Future

We have a method that asynchronously retrieves userModel2, simulates a network request delay of two seconds, and finally modifies the name and returns userModel2


import 'package:flutter_provider_example/future_provider_example/user_model2.dart';

class UserFuture {

  Future<UserModel2> asyncGetUserModel2() async {
    await Future.delayed(Duration(milliseconds: 2000));
    return UserModel2(name: "Get new data"); }}Copy the code

Step 3: Application entry setup

InitialData is the default. The create parameter we pass a Future

because it receives the model create

? >

return FutureProvider<UserModel2>(
  initialData: UserModel2(name: "hello"),
  create: (_) => UserFuture().asyncGetUserModel2(),
  child: MaterialApp(
    debugShowCheckedModeBanner: false,
    home: FutureProviderExample(),
  ),
);
Copy the code

Step 4: Use shared data

import 'package:flutter/material.dart';
import 'package:flutter_provider_example/future_provider_example/user_model2.dart';
import 'package:provider/provider.dart';

class FutureProviderExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("FutureProviderExample"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Consumer<UserModel2>(
              builder: (_, userModel, child) {
                return Text(userModel.name ?? "",
                    style: TextStyle(
                        color: Colors.red,
                        fontSize: 30)); }, ), Consumer<UserModel2>( builder: (_, userModel, child) {return Padding(
                  padding: EdgeInsets.all(20),
                  child: ElevatedButton(
                    onPressed: (){
                      userModel.changeName();
                    },
                    child: Text("Change value"),),); },),],),),); }}Copy the code

The results

We can see that first we show the default value hello, and then finally when we get the result we show that we get the new data, and we try to change the value, but we don’t refresh the UI.

StreamProvider

Streamproviders provide stream values, around the StreamBuilder, that replace new values as they are passed in. As with FutureProvider, the main difference is that values rebuild the UI based on multiple triggers.

If you don’t know much about StreamBuilder, it’s hard to understand StreamProvider, the StreamProvider document address

Step 1: Create the model

class UserModel3{

  UserModel3({this.name});

  String? name = "Jimi";

  void changeName() {
    name = "hello"; }}Copy the code

Step 2: Provide a Stream

The following code acts like a timer, generating a number every second

import 'package:flutter_provider_example/stream_provider_example/user_model3.dart';

class UserStream {

  Stream<UserModel3> getStreamUserModel() {
    return Stream<UserModel3>.periodic(Duration(milliseconds: 1000),
        (value) => UserModel3(name: "$value")
    ).take(10); }}Copy the code

Step 3: Application entry setup

There is also an initial value of initialData, similar to FutureProvider, except that the create property gets a Stream.

return StreamProvider<UserModel3>(
  initialData: UserModel3(name: "hello"),
  create: (_) => UserStream().getStreamUserModel(),
  child: MaterialApp(
    debugShowCheckedModeBanner: false,
    home: StreamProviderExample(),
  ),
);
Copy the code

Step 4: Use shared data

import 'package:flutter/material.dart';
import 'package:flutter_provider_example/stream_provider_example/user_model3.dart';
import 'package:provider/provider.dart';

class StreamProviderExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("StreamProviderExample"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Consumer<UserModel3>(
              builder: (_, userModel, child) {
                return Text(userModel.name ?? "",
                    style: TextStyle(
                        color: Colors.red,
                        fontSize: 30)); }, ), Consumer<UserModel3>( builder: (_, userModel, child) {return Padding(
                  padding: EdgeInsets.all(20),
                  child: ElevatedButton(
                    onPressed: (){
                      userModel.changeName();
                    },
                    child: Text("Change value"),),); },),],),),); }}Copy the code

The results

MultiProvider

In the above example, we only return one provider. In the actual development process, there will certainly be multiple providers. Although we can use the nested way to solve the problem, it will undoubtedly be messy and poor readability. This is where the powerful MultiProvder comes in. Let’s take a look at an example:

Step 1: Create two models

import 'package:flutter/material.dart';

class UserModel1 with ChangeNotifier {

  String name = "Jimi";

  void changeName() {
    name = "hello"; notifyListeners(); }}class UserModel4 with ChangeNotifier {

  String name = "Jimi";
  int age = 18;

  void changeName() {
    name = "hello";
    age = 20; notifyListeners(); }}Copy the code

Step 2: Application entry setup

Compared to method 1, which is nested, method 2 is much simpler.

Method 1: Nested Settings

return ChangeNotifierProvider<UserModel1>(
  create: (_) => UserModel1(),
  child: ChangeNotifierProvider<UserModel4>(
    create: (_) => UserModel4(),
    child: MaterialApp(
      debugShowCheckedModeBanner: false,
      home: MultiProviderExample(),
    ),
  ),
);
Copy the code

Method 2: Use MultiProvider

return MultiProvider(
  providers: [
    ChangeNotifierProvider<UserModel1>(create: (_) => UserModel1()),
    ChangeNotifierProvider<UserModel4>(create: (_) => UserModel4()),
    /// Add more
  ],
  child: MaterialApp(
    debugShowCheckedModeBanner: false,
    home: MultiProviderExample(),
  ),
);
Copy the code

Step 3: Use shared data

import 'package:flutter/material.dart';
import 'package:flutter_provider_example/change_notifier_provider_example/user_model1.dart';
import 'package:flutter_provider_example/multi_provider_example/user_model4.dart';
import 'package:provider/provider.dart';

class MultiProviderExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("MultiProviderExample"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Consumer<UserModel1>(
              builder: (_, userModel, child) {
                return Text(userModel.name,
                    style: TextStyle(
                        color: Colors.red,
                        fontSize: 30)); }, ), Consumer<UserModel4>( builder: (_, userModel, child) {return Text(userModel.age.toString(),
                    style: TextStyle(
                        color: Colors.green,
                        fontSize: 30)); }, ), Consumer2<UserModel1, UserModel4>( builder: (_, userModel1, userModel4, child) {return Padding(
                  padding: EdgeInsets.all(20),
                  child: ElevatedButton(
                    onPressed: (){
                      userModel1.changeName();
                      userModel4.changeName();
                    },
                    child: Text("Change value"),),); },),],),),); }}Copy the code

The results

ProxyProvider

When we have multiple models, there will be cases where the model depends on another model, in which case we can use ProxyProvider to get values from another provider and then inject them into another provider. Let’s look at a code demo

Step 1: Create two models

We created two models, UserModel5 and WalletModel. WalletModel relies on UserModel5. When we call WalletModel’s changeName method, we will change the name of UserModel5. Of course, we are not so simple in the actual development process, here is just to demonstrate how to use ProxyProvider when model dependency

import 'package:flutter/material.dart';

class UserModel5 with ChangeNotifier {

  String name = "Jimi";

  void changeName({required StringnewName}) { name = newName; notifyListeners(); }}class WalletModel {

  UserModel5? userModel5;

  WalletModel({this.userModel5});

  voidchangeName() { userModel5? .changeName(newName:"JIMI"); }}Copy the code

Step 2: Application entry setup

return MultiProvider(
  providers: [
    ChangeNotifierProvider<UserModel5>(create: (_) => UserModel5()),
    ProxyProvider<UserModel5, WalletModel>(
      update: (_, userModel5, walletModel) => WalletModel(userModel5: userModel5),
    )
  ],
  child: MaterialApp(
    debugShowCheckedModeBanner: false,
    home: ProxyProviderExample(),
  ),
);
Copy the code

Step 3: Use shared data

import 'package:flutter/material.dart';
import 'package:flutter_provider_example/proxy_provider_example/user_model5.dart';
import 'package:flutter_provider_example/proxy_provider_example/wallet_model.dart';
import 'package:provider/provider.dart';

class ProxyProviderExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("ProxyProviderExample"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Consumer<UserModel5>(
              builder: (_, userModel, child) {
                return Text(userModel.name,
                    style: TextStyle(
                        color: Colors.red,
                        fontSize: 30)); }, ), Consumer<UserModel5>( builder: (_, userModel, child) {return Padding(
                  padding: EdgeInsets.all(20),
                  child: ElevatedButton(
                    onPressed: (){
                      userModel.changeName(newName: "hello");
                    },
                    child: Text("Change value"),),); }, ), Consumer<WalletModel>( builder: (_, walletModel, child) {return Padding(
                  padding: EdgeInsets.all(20),
                  child: ElevatedButton(
                    onPressed: (){
                      walletModel.changeName();
                    },
                    child: Text("Change values by proxy"),),); },),],),),); }}Copy the code

The results

ChangeNotifierProxyProvider

As with ProxyProvider, the only difference is that it builds and synchronizes ChangeNotifier’s ChangeNotifierProvider, which reconstructs the UI when the provider data changes.

Here’s an example:

  • Get a list of books
  • Gets a list of favorite books
  • Click on a book to add or remove it
  • Refactoring the UI in real time through the agent

Step 1: Create two models

1, BookModel

The BookModel user stores model data and transforms books into models.

class BookModel {
  
  static var _books = [
    Book(1."The number of named nights."),
    Book(2."Big bang beats more people."),
    Book(3."The gate"),
    Book(4."Great Wei Scholar"),
    Book(5."My brother is so steady."),
    Book(6."Beyond deep Space.")];// Get the book length
  int get length => _books.length;

  // Get the book by ID
  Book getById(int id) => _books[id - 1];

  // Get data from index
  Book getByPosition(int position) => _books[position];

  / / more...
}

class Book {
  final int bookId;
  final String bookName;
  
  Book(this.bookId, this.bookName);
}
Copy the code

2, BookManagerModel

BookManagerModel is used to manage, save, and unsave books

import 'package:flutter/material.dart';
import 'package:flutter_provider_example/change_notifier_proxy_provider_example/models/book_model.dart';

class BookManagerModel with ChangeNotifier {

  / / rely on bookModel
  final BookModel _bookModel;

  // Get all the ids of the data
  List<int>? _bookIds;

  // constructor
  BookManagerModel(this._bookModel, {BookManagerModel? bookManagerModel}) : _bookIds = bookManagerModel? ._bookIds ?? [];// Get all the books
  List<Book> getbooks => _bookIds! .map((id) => _bookModel.getById(id)).toList();// Get data from index
  Book getByPosition(int position) => books[position];

  // Get the length of the book
  int getlength => _bookIds? .length ??0;

  // Add books
  voidaddFaves(Book book) { _bookIds! .add(book.bookId); notifyListeners(); }// Delete books
  void removeFaves(Book book) {
    _bookIds!.remove(book.bookId);
    notifyListeners();
  }
}
Copy the code

Step 2: Application entry setup

return MultiProvider(
  providers: [
    Provider(create: (_) => BookModel()),
    ChangeNotifierProxyProvider<BookModel, BookManagerModel>(
      create: (_) => BookManagerModel(BookModel()),
      update: (_, bookModel, bookManagerModel) => BookManagerModel(bookModel),
    )
  ],
  child: MaterialApp(
    debugShowCheckedModeBanner: false,
    home: ChangeNotifierProxyProviderExample(),
  ),
);
Copy the code

Step 3: Set the BottomNavigationBar

import 'package:flutter/material.dart';
import 'package:flutter_provider_example/change_notifier_proxy_provider_example/pages/page_a.dart';
import 'package:flutter_provider_example/change_notifier_proxy_provider_example/pages/page_b.dart';

class ChangeNotifierProxyProviderExample extends StatefulWidget {
  @override
  _ChangeNotifierProxyProviderExampleState createState() => _ChangeNotifierProxyProviderExampleState();
}

class _ChangeNotifierProxyProviderExampleState extends State<ChangeNotifierProxyProviderExample> {


  var _selectedIndex = 0;
  var _pages = [PageA(), PageB()];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: _pages[_selectedIndex],
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _selectedIndex,
        onTap: (index) {
          setState(() {
            _selectedIndex = index;
          });
        },
        items: [
          BottomNavigationBarItem(
            icon: Icon(Icons.book),
            label: "List of Books"
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.favorite),
            label: "Collection")],),); }}Copy the code

Step 4: Book list UI construction

import 'package:flutter/material.dart';
import 'package:flutter_provider_example/change_notifier_proxy_provider_example/models/book_manager_model.dart';
import 'package:flutter_provider_example/change_notifier_proxy_provider_example/models/book_model.dart';
import 'package:flutter_provider_example/change_notifier_proxy_provider_example/widgets/book_item.dart';
import 'package:provider/provider.dart';

class PageA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    
    var bookModel = Provider.of<BookModel>(context);
    
    return Scaffold(
      appBar: AppBar(
        title: Text("List of Books"),
      ),
      body: ListView.builder(
        itemCount: bookModel.length,
        itemBuilder: (_, index) => BookItem(id: index + 1),),); }}Copy the code

Step 5: Favorites list UI construction

import 'package:flutter/material.dart';
import 'package:flutter_provider_example/change_notifier_proxy_provider_example/models/book_manager_model.dart';
import 'package:flutter_provider_example/change_notifier_proxy_provider_example/widgets/book_item.dart';
import 'package:provider/provider.dart';

class PageB extends StatelessWidget {
  @override
  Widget build(BuildContext context) {

    var bookManagerModel = Provider.of<BookManagerModel>(context);
    var bookCount = bookManagerModel.length;

    return Scaffold(
      appBar: AppBar(
        title: Text("Favorites List"), ), body: ListView.builder( itemCount: bookCount, itemBuilder: (_, index) => BookItem(id: bookManagerModel.getByPosition(index).bookId), ), ); }}Copy the code

Other helper wrapper classes

import 'package:flutter/material.dart';
import 'package:flutter_provider_example/change_notifier_proxy_provider_example/models/book_manager_model.dart';
import 'package:flutter_provider_example/change_notifier_proxy_provider_example/models/book_model.dart';
import 'package:provider/provider.dart';

class BookButton extends StatelessWidget {
  
  final Book book;
  
  BookButton({
    Key? key,
    required this.book
  }) : super(key: key);
  
  @override
  Widget build(BuildContext context) {
    
    var bookManagerModel = Provider.of<BookManagerModel>(context);
    
    return GestureDetector(
      onTap: bookManagerModel.books.contains(this.book)
          ?  () => bookManagerModel.removeFaves(this.book)
          :  () => bookManagerModel.addFaves(this.book),
      child: SizedBox(
        width: 100,
        height: 60,
        child: bookManagerModel.books.contains(this.book) ? Icon(Icons.star, color: Colors.red,) : Icon(Icons.star_border), ), ); }}Copy the code
import 'package:flutter/material.dart';
import 'package:flutter_provider_example/change_notifier_proxy_provider_example/models/book_model.dart';
import 'package:flutter_provider_example/change_notifier_proxy_provider_example/widgets/book_button.dart';
import 'package:provider/provider.dart';

class BookItem extends StatelessWidget {

  final int id;

  BookItem({
    Key? key,
    required this.id
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {

    var bookModel = Provider.of<BookModel>(context);
    var book = bookModel.getById(id);

    return ListTile(
      leading: CircleAvatar(
        child: Text("${book.bookId}"),
      ),
      title: Text("${book.bookName}", style: TextStyle( color: Colors.black87 ), ), trailing: BookButton(book: book), ); }}Copy the code

The results

ListenableProxyProvider

Listenablexyprovider is a variant of ListenableProvider, but it works surprisingly well as ChangeNotifierProvider in use, so if you have a better understanding of Listenablexyprovider, Please contact me to add.

conclusion

Provider provides us with a very large number of providers, eight in all. But we more commonly used is ChangeNotifierProvider, MultiProvider, ChangeNotifierProxyProvider, about other providers according to their practical application scenario.