【Flutter 程式設計入門實戰 30 天】Day 29:Provider 狀態管理


【Flutter 程式設計入門實戰 30 天】線上教學課程目錄 使用 Dart 程式語言,開發 Android 和 iOS APP 應用程式。

哈囉~大家好,我是 KT ,今天實戰第二十九天,KT 將為大家來介紹,Provider 狀態管理。

狀態管理介紹

APP 應用程式常需要在不同的頁面中,共享應用程式的狀態資料,例如下方展示簡單的電商購物畫面,當使用者登入後,可以在商品介紹頁,挑選喜愛的商品,加入購物車,切換到購物車時可以看到剛剛加入的清單與項目,可以想像一下,當最後要結帳購買時,若使用者已登入,使用者不需要在最後結帳頁再次輸入帳號相關資料,會從一開始登入畫面時,就已經將相關狀態資料帶到結帳頁。簡單說就是在特定頁面新增或修改相關資料狀態,想要在其他頁面共享資料狀態,就可以透過今天狀態管理的功能來實作出此項功能。

購物車應用

若想學習此購物車範例應用,可以參考 Flutter 官方實作範例 github 原始碼:provider_shopper

狀態管理應用

狀態管理常應用在以下這些功能:
  • 使用者偏好設定選項
  • 使用者登入資料
  • 電商購物車資料

狀態層級

狀態管理我們通常會放在 widget 元件樹中的最上層,好方便可以輕鬆的傳遞到其他底層頁面,以上方購物車應用範例中,我們可以看到 cart 購物車狀態放在 widget 元件樹中的最上層。當我們在購物項目中變化時,前往購物車頁則可以獲取新的變化資料。

Provider 套件

在 2019 Google I/O 官方大力推薦,狀態管理建議採用 Provider 套件來取代之前的 Provide 。提高程式碼維護性,使結構更清晰,更重要的是提供單向數據流,實現更小單元的刷新,大幅提升整體運作性能。
provider 有四個重要的概念:
  • ChangeNotifier
  • ChangeNotifierProvider
  • Consumer
  • Provider.of

ChangeNotifier

主要用在向已註冊的監聽者發送通知,ChangeNotifier 會呼叫 notifyListeners() 來通知狀態已改變,換句話說你可以註冊你想監聽變化狀態,當狀態改變,則可以透過此收到更新通知。

ChangeNotifierProvider

主要用在向其他子節點,底層的頁面, 提供 ChangeNotifier 的實例 (instance),所以 ChangeNotifierProvider 需放在要訪問狀態的元件之上。而如果想提供多個狀態可以使用 MultiProvider。

Consumer

當我們註冊的 ChangeNotifier 與 ChangeNotifierProvider關聯起來後,當發生狀態變化時,我們可以將要變化的資料透過 Consumer 來接收更改對應資料。而最好將 Consumer 放在 widget 樹較低的位置上。才不會 UI 上任何一點小變化,就需要全盤重新構建整個 widget。

Provider.of

因為透過 Consumer 更新資料會讓整體框架整個重構,有時我們只是需要訪問特定資料或調用特定方法,此時可以使用 Provider.of 並且將listen 設定為 false,則可以訪問特定數據,又不會讓整體 UI 框架重構。

加入 Provider 套件

在 pubspec.yaml 中加入 Provider 套件,
provider: ^3.1.0
PS. ^表示與當前大版號一致的版本,〜表示和當前小版號一致的版本。

加入位置實際範例

範例:使用 Provider 監聽計時器數字變化狀態

我們將建立兩個頁面:HomePage 和 BPage。HomePage 會顯示目前計數值,另外有一個按鈕,點擊可以前往 BPage。BPage 點擊右下角按鈕,計數值將會累加一,返回 HomePage 則可以同步獲取剛剛的計數變化值。

執行畫面

建立 ChangeNotifier

新增一個檔名 MyCountChangeNotifier 的 Dart 檔案。
import 'package:flutter/foundation.dart';

class MyCountChangeNotifier with ChangeNotifier {

  // 設定一個整數私有變數 _count的欄位,初值為零
  int _count = 0;

  //可以透過 Consumer 來獲得當下 count 值
  int get count => _count;
  
  //當點擊右下角+ 浮動按鈕,會呼叫此方法
  //此方法會將 _count 累加 1,並叫 notifyListeners
  increment() {
    _count++;
    notifyListeners();
  }
}

使用 Provider.of 獲取資料

//透過 Provider.of 來獲取資料
final counter = Provider.of<MyCountChangeNotifier>(context);

使用 Consumer 來接收更改對應資料

Consumer 接收一個參數、若有兩個參數使用 Consumer2,三個參數使用 Consumer3 以此類推,目前最多支援到 Consumer6,接收六個參數。
 child: Consumer<MyCountChangeNotifier>(builder: (context, counter, _) {
     ...
     ...
     ...
 }

完整程式碼

Main.dart

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

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //在 widget 元件樹中的最上層,使用 provider ,方便傳遞到其他底層頁面
    //KT 建議採用 MultiProvider,因為一個 APP 很少一個 provider 就夠用,所以直接上 MultiProvider 。
    return MultiProvider(
      providers: [ChangeNotifierProvider.value(value: MyCountChangeNotifier())],
      child: MaterialApp(
        home: HomePage(),
      ),
    );
  }
}

//
class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //透過 Provider.of 來獲取資料
    final counter = Provider.of<MyCountChangeNotifier>(context);

    return Scaffold(
      appBar: AppBar(
        title: Text('HKT 線上教室'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              '目前計數值: ${counter.count}',
            ),
            RaisedButton(
              //點擊按鈕後,導轉跳到B頁
              onPressed: () => Navigator.of(context).push(
                MaterialPageRoute(builder: (context) => BPage()),
              ),
              child: Text('跳到B頁'),
            ),
          ],
        ),
      ),
    );
  }
}

class BPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('B頁'),
      ),
      body: Center(
        // 透過 Consumer 來接收更改對應資料
        child: Consumer<MyCountChangeNotifier>(builder: (context, counter, _) {
          return Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text(
                '目前計數值:',
              ),
              Text(
                '${counter.count}',
              ),
            ],
          );
        }),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // 使用 Provider.of,並且將 listen 設定為 false(若沒設定,預設為 true),
          // 則不會再次調用 Widget 重新構建( build )畫面 ,更省效能。
          Provider.of<MyCountChangeNotifier>(context, listen: false)
              .increment();
        },
        child: const Icon(Icons.add),
      ),
    );
  }
}

MyCountChangeNotifier.dart

import 'package:flutter/foundation.dart';

class MyCountChangeNotifier with ChangeNotifier {

  // 設定一個整數私有變數 _count的欄位,初值為零
  int _count = 0;

  //可以透過 Consumer 來獲得當下 count 值
  int get count => _count;
  
  //當點擊右下角+ 浮動按鈕,會呼叫此方法
  //此方法會將 _count 累加 1,並叫 notifyListeners
  increment() {
    _count++;
    notifyListeners();
  }
}

那今天【iT邦幫忙鐵人賽】就介紹到這邊囉~
順帶一提,KT 線上教室,臉書粉絲團,會不定期發佈相關資訊,不想錯過最新資訊,不要忘記來按讚,加追蹤喔!也歡迎大家將這篇文章分享給更多人喔。
我們明天見囉!!!掰掰~

參考資料

HKT 線上教室
http://tw-hkt.blogspot.com/
Background vector created by freepik
https://www.freepik.com

這個網誌中的熱門文章

2023 最新入門零基礎 Kotlin教學【從零開始學 Kotlin 程式設計】Kotlin 教學課程目錄 (Android Kotlin, IntelliJ IDEA, Android Studio, Android APP 開發教學)

nano 文字編輯器

2022 最新入門零基礎 Flutter教學 【Flutter 程式設計入門實戰 30 天】Flutter 教學課程目錄 (IntelliJ IDEA 開發教學)

16天記下7000單字

最新入門零基礎 Java 教學【從零開始學 Java 程式設計】Java教學課程目錄 (IntelliJ IDEA 開發教學)