iT邦幫忙

2022 iThome 鐵人賽

DAY 17
0
Mobile Development

Flutter 30: from start to store系列 第 17

Flutter介紹:App狀態管理 - app state management

  • 分享至 

  • xImage
  •  

昨天遇到了頁面間資料交換的問題,所以今天我們要來在app裡面建立跨頁面的資料分享機制:

  • 原本的頁面關係

  • MainPage點選AstroPicture的愛心按鈕之後,要將資料交給FavoritePage並可以導頁到收藏細節頁

這次會使用官方推薦的Provider套件來解決這個問題

好的,那我們就開始吧!


Provider概述

  • pubspec.yaml內加入provider並下載套件:

    $ flutter pub add provider
    
    $ flutter pub get
    
  • provider的使用分為幾個階段:

    1. 設定一個「狀態管理組件」(ChangeNotifierProvider),放在想要傳遞資料以及使用資料的組件(在專案中是指MainPage, FavoritePage)的上層。
    2. 接著,「狀態管理組件」會創建一筆「可訂閱的資料模型」(extends ChangeNotifier),也就是我們這次指稱的app state,供各組件訂閱使用(Comsumer)或操作(Provider.of)。
    3. 當資料被修改,ChangeNotifier可以通知訂閱此資料模型的組件,依照最新的資料重繪。
  • provider的底層實踐自Inherited Widget,可以和子組件共享資料,並在資料變化時通知使用該資料的子組件重繪,詳見 InheritedWidget


ChangeNotifier

  • 用於向訂閱的組件發出資料變化通知
  • 在要建立的data model加入ChangeNotifier,以官方提供的購物車資料模型為例:
    class CartModel extends ChangeNotifier {
      final List<Item> _items = [];
    
      UnmodifiableListView<Item> get items => UnmodifiableListView(_items);
    
      int get totalPrice => _items.length * 42;
    
    
      void add(Item item) {
        _items.add(item);
        notifyListeners(); // 通知訂閱CartModel的子組件資料已變更,需重繪
      }
    
      void removeAll() {
        _items.clear();
        notifyListeners(); // 通知訂閱CartModel的子組件資料已變更,需重繪
      }
    }
    

ChangeNotifierProvider

  • 可以提供「資料模型實例」給子組件,所有的組件都依照這個實例內部的資料變化進行繪製
    void main() {
      runApp(
        ChangeNotifierProvider(
          create: (context) => CartModel(),
          child: const MyApp(), // 在MyApp class之中可以透過Consumer來訂閱CartModel實例,或者Provider.of來使用CartModel內部的Method
        ),
      );
    }
    
  • 如果要切分為多種不同的資料模型,可以使用MultiProvider:
    void main() {
      runApp(
        MultiProvider(
          providers: [
            ChangeNotifierProvider(create: (context) => CartModel()), // ChangeNotifierProvider具備在資料變更時通知組件重繪的功能
            Provider(create: (context) => SomeOtherClass()), // Provider只負責取data model裡面的值,不會觸發組件重繪
          ],
          child: const MyApp(), // MyApp之中可以取用兩種data model實例的資料
        ),
      );
    }
    

Consumer

  • 在子組件中取用Provider/ChangeNotifierProvider提供的資料
  • 比如說上面我在MyApp外層包了ChangeNotifierProvider,在MyApp繪製UI時我可以建立Comsumer取得CartModel()實例內部的值
    // MyApp.dart
    @override
    Widget build(BuildContext context){
        return HumongousWidget(
          child: AnotherMonstrousWidget(
            child: Consumer<CartModel>( // 定義Consumer,取得上層傳下來的CartModel()實例
              builder: (context, cart, child) { // 在第二個parameter將該實例指稱為cart
                return Text('Total price: ${cart.totalPrice}'); // 取用cart內部的totalPrice值
              },
            ),
          ),
        );
    }
    
    如上,若是組件使用的值被變更時,有註明要notifyListener(),則會觸發該組件的重繪
  • 要注意的是,在資料變更且被通知重繪時,Consumer包覆的子組件全部都會重繪。由於子組件使用的結構愈繁複、層級越多,重繪的資源消耗就會比較大,因此若要增進效能,最好將Consumer包在較低層的組件上
    // better: 只觸發Text重繪
    WidgetA(
        child: WidgetB(
            child: WidgetC(
                child: Consumer<DataModel>( 
                  builder: (context, dataModel, child) {
                    return Text(dataModel.value); 
                  }
                )
            )
        )
    )
    
    
    // worse: 觸發WidgetA, WidgetB, WidgetC, Text重繪
    Consumer<DataModel>( 
      builder: (context, dataModel, child) {
       return WidgetA(
                child: WidgetB(
                    child: WidgetC(
                        child: Text(dataModel.value)
                        )
                    )
                )
      }
    )
    

Provider.of

  • 有時候我們不需要使用到實例中的資料,只是要操作該實例提供的方法而已,就可以使用Provider.of:
    class _MyAppState extends State<MyApp> {
        void _cleanCart(BuildContext context){
            // 只是要調用「清除購物車中所有的項目」方法,並沒有要取用購物車中的資料
            Provider.of<CartModel>(context, listen: false).removeAll();
        }
    
        @override
        Widget build(BuildContext context){
            return TextButton(
                onPress(){
                    _cleanCart(context);
                },
                child: Text('Clean Cart!')
            )
        }
    }
    

Recap

今天我們看到了如何使用Provider套件來實現組件間的溝通,幾個關鍵的元素分別為

  • ChangeNotifier:讓資料模型在內部資料變更時,能夠通知有訂閱的組件
  • ChangeNotifierProvider:放在最上層,建立資料模型的實例供子組件取用
  • Consumer: 讓子組件能夠取得資料
  • Provider.of: 不取用資料,只調用資料模型提供的方法

在App愈上層的地方加入ChangeNotifierProvider,能共享資料模型的組件的範圍就愈大,但是同樣要傳愈多資料給其他根本用不到這些資料的組件。

明天要來為APP加入第三方套件:table_calendar,開始刻畫我們的月曆頁囉!


上一篇
Flutter介紹:頁面的建構 - Custom Widget
下一篇
Flutter介紹:新增別人寫好的酷炫功能 - flutter package
系列文
Flutter 30: from start to store30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言