【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

留言

這個網誌中的熱門文章

16天記下7000單字

Costco 好市多 AFTERSHOKZ 骨傳導 藍牙運動耳機 AS650 開箱實測評價與心得

2019 最新入門零基礎【從零開始學 Java 程式設計】線上教學課程目錄 (7/5 更新囉~)