Flutter 使用 Provider 進行狀態管理(主題模式切換示例)

狀態管理

Flutter頁面是有widget構成的,在widget中就有有狀態 widget無狀態 widget。這裏的狀態是State,翻看源碼可以看到State的描述是這樣的:(1)在構建widget時可以同步讀取(2)在widget的生存期內可能會更改。widget實現者的職責是確保使用State.setState在狀態改變時迅速得到通知。因此狀態管理就是在state變化時迅速通知到widget的實現着,讓其根據通知做出相應的處理。

上篇文章(Flutter widget 間傳遞數據的方案 InheritedWidget、Notification、EventBus )我們瞭解了數據在widget之間的傳遞,文中的方案對狀態的管理耦合度都比較高。

早就聽聞官方推薦的 Provider 庫和 阿里出品的 fish-redux 庫通過本篇我們來學習大廠們是如何對state進行管理的。

Provider

Provider 4.0.5:依賴項注入(DI)和狀態管理之間的混合,使用widgets構建。

通過使用widgets進行狀態管理,provider可以保證:

  • 可維護性,通過強制的單向數據流實現
  • 可測試性/可組合性,因爲始終可以模擬/覆蓋值
  • 健壯性,因爲很難忘記處理模型/小部件的更新方案

使用文檔

暴露(創建)值

創建新的對象
  • 推薦做法:在create內創建一個新對象
Provider(
  create: (_) => new MyModel(),
  child: ...
)
  • 不推薦做法:使用Provider.value創建對象。
ChangeNotifierProvider.value(
  value: new MyModel(),
  child: ...
)

不要使用隨時間變化的變量來創建對象。原因是在這種情況下,變量更改時將永遠不會更新您的對象

int count;

Provider(
  create: (_) => new MyModel(count),
  child: ...
)

如果要將隨時間變化的變量傳遞給對象,請考慮使用ProxyProvider

int count;

ProxyProvider0(
  update: (_, __) => new MyModel(count),
  child: ...
)
使用已存在的對象

如果已經有對象實例並想要公開它,則應使用提供程序的.value構造函數。否則,可能會在對象仍在使用時調用其dispose方法。

  • 推薦做法:使用ChangeNotifierProvider.value提供一個現有的ChangeNotifier
MyChangeNotifier variable;

ChangeNotifierProvider.value(
  value: variable,
  child: ...
)
  • 不推薦做法:使用默認構造函數重用現有的ChangeNotifier
MyChangeNotifier variable;

ChangeNotifierProvider(
  create: (_) => variable,
  child: ...
)

讀取值

讀取值的最簡單方法是使用靜態方法Provider.of <T>(BuildContext context)。該方法從傳遞BuildContextwidget開始,在widget tree中查找如果找到,它將返回找到的T類型的最近變量(如果未找到,則拋出該異常)。

結合暴露一個值的第一個示例,此widget將讀取顯示的String並呈現“ Hello World”

class Home extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text(
      // String 類型
      Provider.of<String>(context)
    );
  }
}

另外,也可以不使用Provider.of,而可以使用ConsumerSelector。 這些對於性能優化或難以獲得providerBuildContext後代很有用。有關更多信息,請參見FAQ或 ConsumerSelector 的文檔。

讀取多個值 MultiProvider

在大型應用程序中注入許多值時,Provider可能會迅速嵌套,如下:

Provider<Something>(
  create: (_) => Something(),
  child: Provider<SomethingElse>(
    create: (_) => SomethingElse(),
    child: Provider<AnotherThing>(
      create: (_) => AnotherThing(),
      child: someWidget,
    ),
  ),
),

這種死亡嵌套,看着就讓人入魔,所以Provider也提供了簡便的寫法:

MultiProvider(
  providers: [
    Provider<Something>(create: (_) => Something()),
    Provider<SomethingElse>(create: (_) => SomethingElse()),
    Provider<AnotherThing>(create: (_) => AnotherThing()),
  ],
  child: someWidget,
)
ProxyProvider

3.0.0開始,提供了一種新的providerProxyProviderProxyProvider是一個provider,它將來自其他provider的多個值組合到一個新對象中,並將結果發送給Provider。 然後,只要新provider之一依賴更新,該新對象就會被更新。

下面的示例使用ProxyProvider來基於來自另一個provider的計數器來構建轉換。

Widget build(BuildContext context) {
  return MultiProvider(
    providers: [
      ChangeNotifierProvider(create: (_) => Counter()),
      ProxyProvider<Counter, Translations>(
        update: (_, counter, __) => Translations(counter.value),
      ),
    ],
    child: Foo(),
  );
}

class Translations {
  const Translations(this._value);

  final int _value;

  String get title => 'You clicked $_value times';
}

它具有多種變體,例如:

  • ProxyProvider vs ProxyProvider2 vs ProxyProvider3,… 類名後面的數字是ProxyProvider依賴的其他providers的數量。
  • ProxyProvider vs ChangeNotifierProxyProvider vs ListenableProxyProvider,… 它們的工作方式都相似,但是ChangeNotifierProxyProvider不會將結果發送到Provider,而是將其值發送到ChangeNotifierProvider

使用文檔總結

首先Provider是依賴注入和狀態管理和混合物,也就是它能實現這兩種功能。依賴注入這裏先暫時不用管,狀態管理纔是文章的重點。

這裏的狀態管理主要是指數據、UI的管理。

看了以上文檔,如果還是懵逼,那麼直接看這裏的使用步驟吧。

Provider簡單的使用可以按如下步驟來:

  • 創建繼承自 ChangeNotifier 的數據類
  • 創建值,創建新對象通過 create的方式,複用對象通過ChangeNotifierProvider.value的方式
  • 獲取值,可通過Provider.of(context)的方式或者ConsumerSelector的方式

簡單示例使用

官方 simple實現的效果是:點擊 + 號按鈕,標題文本和內容文本都會更新顯示次數。 爲了更好的理解Provider的使用,可看下面的代碼

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class ProviderTestPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
  // 使用 ChangeNotifierProvider.value 提供一個現有的ChangeNotifier 創建 Provider 
    return ChangeNotifierProvider.value(
      value: Counter(),
      child: MaterialApp(
        home: const MyHomePage(),
      )
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Title()),
      body: const Center(child: CounterLabel()),
      floatingActionButton: const IncrementCounterButton(),
    );
  }
}
/// + 號按鈕 widget
class IncrementCounterButton extends StatelessWidget {
  const IncrementCounterButton({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return FloatingActionButton(
      onPressed: () {
        // listen: false 必須設置
        Provider.of<Counter>(context,listen: false).increment();
      },
      tooltip: 'Increment',
      child: const Icon(Icons.add),
    );
  }
}
/// 文本內容 widget
class CounterLabel extends StatelessWidget {
  const CounterLabel({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final counter = Provider.of<Counter>(context);
    return Column(
      mainAxisSize: MainAxisSize.min,
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        const Text(
          'You have pushed the button this many times:',
        ),
        Text(
          '${counter.count}',
          // ignore: deprecated_member_use
          style: Theme.of(context).textTheme.display1,
        ),
      ],
    );
  }
}

/// 標題 widget
class Title extends StatelessWidget {
  const Title({Key key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    final counter = Provider.of<Counter>(context);
    return Text('Tapped ${counter.count} times');
  }
}
/// 1. 數據類
class Counter with ChangeNotifier {
  int _count = 0;
  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }
}

使用 Provider 實現主題切換示例

上篇文章寫過 EventBus 實現主題切換 https://blog.csdn.net/ITxiaodong/article/details/105083216,這次使用 Provider 也實現主題切換的功能。

完成下面幾個步驟即可實現:

  1. 定義類extends或者with ChangeNotifier,如:點擊查看 theme.dart 的實現
  2. 封裝Store類,用於所有provider數據的初始化。如:點擊查看 store.dart 的實現
  3. main.dart文件中聲明爲全局的對象,並通過Consumer去調用。如:點擊查看 main.dart 的實現
  4. 我的頁面點擊修改主題後出現彈窗並選擇顏色。如:點擊查看 mine_page.dart 的實現

爲了閱讀體驗,不粘貼代碼了。
具體源碼請跳轉項目查看:https://github.com/YGragon/Flutter-WanAndroid

參考

上車

佛系原創號主
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章