头图

Article series

Flutter Provider state management---introduction, class diagram analysis, basic use

Flutter Provider State Management---Analysis of the Use of Eight Providers

Flutter Provider State Management---Analysis of

Flutter Provider state management---MVVM architecture combat

Video series

Flutter Provider State Management---Introduction, class diagram analysis, basic use of

Flutter Provider State Management---Analysis of the Use of Eight Providers

Flutter Provider State Management---Analysis of

Flutter Provider State Management---MVVM Architecture Actual Combat

Source code warehouse address

github warehouse address

Preface

In our last article, we Provider and explained the class structure, and finally wrote a simple example. Through the previous chapter, we Provider . In this chapter, let’s talk about it. The 8 types of providers of Provider

Provider

Provider is the most basic Provider component. You can use it to provide a value for any position in the component tree, but when the value changes, it will not update the UI. Below we give an example

Step 1: Create a model

class UserModel {

  String name = "Jimi";

  void changeName() {
    name = "hello";
  }
}

Step 2: Application entry settings

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

Step 3: Use shared data

Regarding Consumer , the consumers will be mentioned later. We only need to know that there are two consumers. The first is used to display the data of the model, and the second is used to change the data of the model.

  • The first Comsumer is the data used to read the model name
  • The second Consumer used to change the data of the model name
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("改变值"),
                  ),
                );
              },
            ),
          ],
        ),
      ),
    );
  }
}

operation result

We click the button to cause the model data to change, but the UI does not change or rebuild after the model data is changed. That's because the Provider provider component does not monitor the changes in the values it provides.

ChangeNotifierProvider

It is different from the Provider ChangeNotifierProvider will monitor the changes of the model object, and when the data changes, it will also rebuild Consumer (consumer), below we give an example

Step 1: Create a model

Careful we can find that there are two changes in the model defined here, as follows:

  • ChangeNotifier with 061663dab24d50
  • Called notifyListeners()

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

import 'package:flutter/material.dart';

class UserModel1 with ChangeNotifier {

  String name = "Jimi";

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

Step 2: Application entry settings

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

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("改变值"),
                  ),
                );
              },
            ),
          ],
        ),
      ),
    );
  }
}

operation result

FutureProvider

In simple terms, FutureProvider used to provide values that may not be ready when its value is ready to be used in the component tree. The main purpose is to ensure that null values are not passed to any subcomponents, and FutureProvider has an initial value, which can be used by subcomponents. Future value and tell the sub-component to use the new value to rebuild.

Note:

  • FutureProvider will only be rebuilt once
  • Default display initial value
  • Then display the Future value
  • Will not rebuild again in the end

Step 1: Create a model

The Provider and 061663dab24eff is that the constructor is added, and changeName becomes Future . We simulate the network request to change its value after a two-second delay.

class UserModel2{

  UserModel2({this.name});

  String? name = "Jimi";

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

Step 2: Provide Future

We have a method, which is to obtain userModel2 asynchronously, the simulated network request is delayed for two seconds, and finally name modified and returned to 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: "获取新的数据");
  }

}

Step 3: Application entry settings

initialData is the default value. For the create parameter, we passed a Future<UserModel2> because it received the model Create<Future<T>?>

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

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("改变值"),
                  ),
                );
              },
            ),
          ],
        ),
      ),
    );
  }
}

operation result

We can see the first show defaults hello , finally get to show the results when obtain new data, we try to change its value, although the value of the change but did not refresh the UI.

StreamProvider

StreamProvider provides a flow value around StreamBuilder . The provided value will replace the new value when it is passed in. Like FutureProvider , the main difference is that the value will rebuild the UI based on multiple triggers.

If you StreamBuilder n’t know much about 061663dab2505a, then it is difficult for you to understand StreamProvider , StreamProvider document address

Step 1: Create a model

class UserModel3{

  UserModel3({this.name});

  String? name = "Jimi";

  void changeName() {
    name = "hello";
  }
}

Step 2: Provide Stream

The following code is similar to 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);
  }
}

Step 3: Application entry settings

There are also initialData initial value, and FutureProvider similar, but create property is to obtain a Stream stream.

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

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("改变值"),
                  ),
                );
              },
            ),
          ],
        ),
      ),
    );
  }
}

operation result

MultiProvider

In the above example, we only returned one provider. In the actual development process, there will definitely be multiple providers. Although we can use nested methods to solve this problem, this is undoubtedly messy and poor in readability. At this time, the powerful MultiProvder is produced. Let's 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();
  }
}

Step 2: Application entry settings

Compared with the nested setting of mode one, mode two is particularly simple.

Method 1: Nested setting

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

Method 2: Use MultiProvider

return MultiProvider(
  providers: [
    ChangeNotifierProvider<UserModel1>(create: (_) => UserModel1()),
    ChangeNotifierProvider<UserModel4>(create: (_) => UserModel4()),
    /// 添加更多
  ],
  child: MaterialApp(
    debugShowCheckedModeBanner: false,
    home: MultiProviderExample(),
  ),
);

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("改变值"),
                  ),
                );
              },
            ),
          ],
        ),
      ),
    );
  }
}

operation result

ProxyProvider

When we have multiple models, there will be cases where the model depends on another model. In this case, we can use ProxyProvider to get a value from another provider, and then inject it into another provider. Let's take a look at the code demo

Step 1: Create two models

Here we created two models UserModel5 and WalletModel , while WalletModel dependence and UserModel5 , when calling WalletModel of changeName when the method changes UserModel5 when the inside of the name, of course, we are not so simple in the actual development process, here is a demonstration model dependent If you use ProxyProvider

import 'package:flutter/material.dart';

class UserModel5 with ChangeNotifier {

  String name = "Jimi";

  void changeName({required String newName}) {
    name = newName;
    notifyListeners();
  }
}


class WalletModel {

  UserModel5? userModel5;

  WalletModel({this.userModel5});

  void changeName() {
    userModel5?.changeName(newName: "JIMI");
  }
}

Step 2: Application entry settings

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

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("改变值"),
                  ),
                );
              },
            ),
            Consumer<WalletModel>(
              builder: (_, walletModel, child) {
                return Padding(
                  padding: EdgeInsets.all(20),
                  child: ElevatedButton(
                    onPressed: (){
                      walletModel.changeName();
                    },
                    child: Text("通过代理改变值"),
                  ),
                );
              },
            ),
          ],
        ),
      ),
    );
  }
}

operation result

ChangeNotifierProxyProvider

The ProxyProvider , the only difference is that it builds and synchronizes ChangeNotifier of ChangeNotifierProvider . When the provider data changes, the UI will be reconstructed.

Below we give an example:

  • Get a list of books
  • Get a list of favorite books
  • Click on the book to add or uncollect
  • Real-time refactoring of UI through proxy

Step 1: Create two models

1、BookModel

BookModel Users store model data and convert books into models.

class BookModel {
  
  static var _books = [
    Book(1, "夜的命名数"),
    Book(2, "大奉打更人"),
    Book(3, "星门"),
    Book(4, "大魏读书人"),
    Book(5, "我师兄实在太稳健了"),
    Book(6, "深空彼岸"),
  ];

  // 获取书籍长度
  int get length => _books.length;

  // 根据ID获取书籍
  Book getById(int id) => _books[id -1];

  // 根据索引获取数据
  Book getByPosition(int position) => _books[position];

  // 更多....
}

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

2、BookManagerModel

BookManagerModel mainly used to manage books, collect books, cancel collections, etc.

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

class BookManagerModel with ChangeNotifier {

  // 依赖bookModel
  final BookModel _bookModel;

  // 获取数据所有的ID
  List<int>? _bookIds;

  // 构造函数
  BookManagerModel(this._bookModel, {BookManagerModel? bookManagerModel})
    : _bookIds = bookManagerModel?._bookIds ?? [];

  // 获取所有的书
  List<Book> get books => _bookIds!.map((id) => _bookModel.getById(id)).toList();

  // 根据索引获取数据
  Book getByPosition(int position) => books[position];

  // 获取书籍的长度
  int get length => _bookIds?.length ?? 0;

  // 添加书籍
  void addFaves(Book book) {
    _bookIds!.add(book.bookId);
    notifyListeners();
  }

  // 删除书籍
  void removeFaves(Book book) {
    _bookIds!.remove(book.bookId);
    notifyListeners();
  }
}

Step 2: Application entry settings

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

Step 3: Set up 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: "书籍列表"
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.favorite),
            label: "收藏"
          )
        ],
      ),
    );
  }
}

The fourth step: UI construction of the book list

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("书籍列表"),
      ),
      body: ListView.builder(
        itemCount: bookModel.length,
        itemBuilder: (_, index) => BookItem(id: index + 1),
      ),
    );
  }
}

Step 5: Build the favorite list UI

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("收藏列表"),
      ),
      body: ListView.builder(
        itemCount: bookCount,
        itemBuilder: (_, index) => BookItem(id: bookManagerModel.getByPosition(index).bookId),
      ),
    );
  }
}

Other auxiliary packaging

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),
      ),
    );
  }
}
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),
    );
  }
}

operation result

ListenableProxyProvider

ListenableProxyProvider is ListenableProvider , but it is surprisingly consistent with ChangeNotifierProvider ListenableProxyProvider , please contact me to add.

Summarize

Provider provides us with a lot of providers, there are a total of eight. But our more commonly used ones are ChangeNotifierProvider , MultiProvider , ChangeNotifierProxyProvider , and other providers can be based on their actual application scenarios.


Jimi
23 声望14 粉丝