Flutter Provider狀態管理---八種提供者使用分析

Jimi發表於2021-10-13

文章系列

Flutter Provider狀態管理---介紹、類圖分析、基本使用

Flutter Provider狀態管理---八種提供者使用分析

Flutter Provider狀態管理---四種消費者使用分析

Flutter Provider狀態管理---MVVM架構實戰

視訊系列

Flutter Provider狀態管理---介紹、類圖分析、基本使用

Flutter Provider狀態管理---八種提供者使用分析

Flutter Provider狀態管理---四種消費者使用分析

Flutter Provider狀態管理---MVVM架構實戰

原始碼倉庫地址

github倉庫地址

前言

在我們上一篇文章中對Provider進行了介紹以及類結構的說明,最後還寫了一個簡單的示例,通過上一章節我們對Provider有了一個基本的瞭解,這一章節我們來說說Provider的8種提供者以及他們的使用區別。

Provider

Provider是最基本的Provider元件,可以使用它為元件樹中的任何位置提供值,但是當該值更改的時候,它並不會更新UI,下面我們給出一個示例

第一步:建立模型

class UserModel {

  String name = "Jimi";

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

第二步:應用程式入口設定

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

第三步:使用共享資料

關於Consumer後面將消費者在提及,我們這裡只需要知道有兩個消費者,第一個用於展示模型的資料,第二個用於改變模型的資料。

  • 第一個Comsumer是用於讀取模型的資料name
  • 第二個Consumer用於改變模型的資料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("改變值"),
                  ),
                );
              },
            ),
          ],
        ),
      ),
    );
  }
}

執行結果

我們點選按鈕的會導致模型資料改變,但是模型資料改變之後UI並沒有變化也沒有重建,那是因為Provider提供者元件不會監聽它提供的值的變化。

ChangeNotifierProvider

它跟Provider元件不同,ChangeNotifierProvider會監聽模型物件的變化,而且當資料改變時,它也會重建Consumer(消費者),下面我們給出一個示例

第一步:建立模型

細心點我們可以發現這裡定義的模型有兩處變化,如下:

  • 混入了ChangeNotifier
  • 呼叫了notifyListeners()

因為模型類使用了ChangeNotifier,那麼我們就可以訪問notifyListeners()並且在呼叫它的任何時候,ChangeNotifierProvider都會收到通知並且消費者將重建UI。

import 'package:flutter/material.dart';

class UserModel1 with ChangeNotifier {

  String name = "Jimi";

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

第二步:應用程式入口設定

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

第三步:使用共享資料

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

執行結果

FutureProvider

簡單來說,FutureProvider用於提供在元件樹中準備好使用其值時可能尚未準備好的值,主要是確保空值不會傳遞給任何子元件,而且FutureProvider有一個初始值,子元件可以使用該Future值並告訴子元件使用新的值來進行重建。

注意:

  • FutureProvider只會重建一次
  • 預設顯示初始值
  • 然後顯示Future
  • 最後不會再次重建

第一步:建立模型

這裡和Provider不同的是增加了建構函式,以及changeName變成了Future,我們模擬網路請求延遲兩秒後改變其值。

class UserModel2{

  UserModel2({this.name});

  String? name = "Jimi";

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

第二步:提供Future

我們有一個方法,就是非同步獲取userModel2,模擬網路請求延遲兩秒執行,最後修改了name並返回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: "獲取新的資料");
  }

}

第三步:應用程式入口設定

initialData是預設值,create引數我們傳了一個Future<UserModel2>,因為它接收的模型Create<Future<T>?>

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

第四步:使用共享資料

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

執行結果

我們可以看到先展示預設值hello,最後獲取到結果的時候展示了獲取新的資料,我們嘗試改變其值,雖然值改變但是並沒有重新整理UI。

StreamProvider

StreamProvider提供流值,是圍繞StreamBuilder,所提供的值會在傳入的時候替換掉新值。和FutureProvider一樣,主要的區別在於值會根據多次觸發重新構建UI。

如果你對StreamBuilder不太瞭解的話,那麼你就很難理解StreamProviderStreamProvider文件地址

第一步:建立模型

class UserModel3{

  UserModel3({this.name});

  String? name = "Jimi";

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

第二步:提供Stream

下面這段程式碼類似計時器,每隔一秒鐘生成一個數字

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

第三步:應用程式入口設定

這裡也有initialData初始值,和FutureProvider類似,只是create屬性是獲取一個Stream流。

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

第四步:使用共享資料

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

執行結果

MultiProvider

在上面的例子中我們都只是返回了一個提供者,在實際開發過程中肯定會有多個提供者,我們雖然可以採用巢狀的方式來解決,但是這樣無疑是混亂的,可讀性級差。這個時候強大的MultiProvder就產生了,我們來看下示例:

第一步:建立兩個模型

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

第二步:應用程式入口設定

相對於方式一這種巢狀方式設定,方式二就顯得尤為簡單。

方式一:巢狀設定

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

方式二:使用MultiProvider

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

第三步:使用共享資料

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

執行結果

ProxyProvider

當我們有多個模型的時候,會有模型依賴另一個模型的情況,在這種情況下,我們可以使用ProxyProvider從另一個提供者獲取值,然後將其注入到另一個提供者中。我們來看下程式碼演示

第一步:建立兩個模型

下面我們建立了兩個模型UserModel5WalletModel,而WalletModel依賴與UserModel5,當呼叫WalletModelchangeName方法時會改變UserModel5裡面的name,當然我們在實際開發的過程中並不是這麼簡單,這裡只是演示模型依賴時如果使用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");
  }
}

第二步:應用程式入口設定

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

第三步:使用共享資料

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("通過代理改變值"),
                  ),
                );
              },
            ),
          ],
        ),
      ),
    );
  }
}

執行結果

ChangeNotifierProxyProvider

ProxyProvider原理一樣,唯一的區別在於它構建和同步ChangeNotifierChangeNotifierProvider,當提供者資料變化時,將會重構UI。

下面我們給出一個例子:

  • 獲取書籍列表
  • 獲取收藏書籍列表
  • 點選書籍可加入或者取消收藏
  • 通過代理實時重構UI

第一步:建立兩個模型

1、BookModel

BookModel使用者儲存模型資料,將書籍轉換成模型。

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主要用於管理書籍、收藏書籍、取消收藏等操作

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

第二步:應用程式入口設定

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

第三步:設定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: "收藏"
          )
        ],
      ),
    );
  }
}

第四步:書籍列表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/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),
      ),
    );
  }
}

第五步:收藏列表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),
      ),
    );
  }
}

其他輔助封裝類

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

執行結果

ListenableProxyProvider

ListenableProxyProviderListenableProvider的一個變體,但是在使用上和ChangeNotifierProvider效果驚人的一致,如果大家對ListenableProxyProvider有更深的理解,請聯絡我補充。

總結

Provider為我們提供了非常多的提供者,總共有八種。但我們比較常用的是ChangeNotifierProviderMultiProviderChangeNotifierProxyProvider,關於其他的提供者可根據自己的實際應用場景來。

相關文章