iT邦幫忙

2024 iThome 鐵人賽

DAY 21
0
Mobile Development

Flutter基礎入門系列 第 21

【Day 21】獲得狀態變更通知並更新UI吧!

  • 分享至 

  • xImage
  •  

接續昨天的內容,本篇將來談談Consumer,並介紹其背後的函式Provider.of與他們間的差異。


訂閱並獲得通知

常見的有以下幾個方法:

  1. Consumer
  2. Provider.of
  3. context.watch (與Provider.of作用相同)

Consumer

Consumer官方文件說明連結

Consumer是package provider中的一個類別,而這個類別會在每次它「訂閱」的ChangeNotifier有更新(也就是ChangeNotifier去「通知」「聽眾」時),會重新執行一次builder中的Widget建構。至於builder,是宣告Consumer時唯一的必要欄位。

因為每次獲得新通知時,builder內的widget將會重新建構,因此這個類別在實作上為了效能考量有以下兩個需要注意的點:

  1. builder所建立的widget盡量簡單化,將一些複雜的建構過程放在builder外面
return Consumer<Foo>(
  builder: (context, foo, child) {
    return Text('Value of Foo: ${foo.value}');
  },
  // build expensive widget here
  child: const SomeExpensiveWidget();
);
  1. Consumer物件盡量放在程式中越深層越好
// BAD
return Consumer<CartModel>(
  builder: (context, cart, child) {
    return HumongousWidget(
      // ...
      child: AnotherMonstrousWidget(
        // ...
        child: Text('Total price: ${cart.totalPrice}'),
      ),
    );
  },
);

// GOOD
return HumongousWidget(
  // ...
  child: AnotherMonstrousWidget(
    // ...
    child: Consumer<CartModel>(
      builder: (context, cart, child) {
        return Text('Total price: ${cart.totalPrice}');
      },
    ),
  ),
);

Consumer widget能夠訂閱與獲得ChangeNotifier的通知,實際上背後是利用Provider.of函式,那麼Provider.of又是什麼呢?

Provider.of

以下內容參考Medium - How to use Provider: Context.read, watch and select

Provider.of與我們之前使用的context.watch十分相似,用法幾乎相同。而剛剛介紹的Consumer其實只是將Provider.of包裝成一個widget,並增加一個builder項目。

用之前第一個程式做範例:

class FavoritesPage extends StatelessWidget {
  @override
  Widget build(context) {
    var appState = context.watch<MyAppState>();
    if (appState.favorites.isEmpty) {
      return Center(child: Text('No favorites Saved'),);
    }

    return Padding(
      padding: const EdgeInsets.fromLTRB(20, 0, 0, 0),
      child: ListView(
        children: [
          Padding(
            padding: const EdgeInsets.all(20), 
            child: Text('You have ${appState.favorites.length} favorites'),
          ),
          for (var wordPair in appState.favorites) 
          ListTile(...),
        ],
      ),
    );
  }
}

Provider.of<MyAppState>(context)或是context.watch<MyAppState>()(二者可互換),獲得MyAppState的更新通知時,將會重新建立Widget build(context) {}中的內容。

Consumer v.s. Provider.of

這時你可能會有個疑問,什麼時候要使用Consumer,什麼時候要選擇Provider.of?
答案就是:隨個人喜好就好了。

flutter在談論App State與Ephemeral state的比較時提到了Redux的開發者,Dan Abramov曾說的話:
https://ithelp.ithome.com.tw/upload/images/20241005/20169446ZLmOLlCRpC.png
基本上,只要覺得怎麼樣寫起來比較順手,就怎麼做吧!

雖然話是這麼說,但兩者間仍有部份使用上的差異,我們所需要做的,便是知道他們的優勢在哪些方面,並利用這些優勢來開發程式。

以下內容擷取自StackOverflow - When to use Provider.of<X> vs. Consumer<X> in Flutter最佳回答
https://ithelp.ithome.com.tw/upload/images/20241005/20169446xgMCnRlDLN.png

因此可以簡單列出兩者各自的優勢及差異:

  • Provider.of
    1. 所有widget中都可呼叫此函式
    2. 不會增加縮排,閱讀性較佳
    3. 當listen=true時,會重新建構整個widget
    4. 必須要有為provider子代(descendant)的BuildContext才可以運作
  • Consumer
    1. 能夠指定需要/不需要重新建構的部份(builder/child),藉此提升效能
    2. 能夠在沒有provider子代(descendant)的BuildContext的情況下也能運作

BuildContext對provider的影響

關於BuildContext這一部份,官方有給出個範例:

下方程式碼為使用Provider.of,而在此Provider.of所得到的BuildContext是屬於該provider的祖先(ancestor),非子代,因此它會丟出ProviderNotFoundException的錯誤。

// ERROR: ProviderNotFound
Widget build(BuildContext context) {
  return ChangeNotifierProvider(
    create: (_) => Foo(),
    child: Text(Provider.of<Foo>(context).value),
  );
}

若是改使用Consumer,則能夠正常執行並建構Text這個widget,且在foo更改(Foo通知訂閱者)時更新Text的內容。

Widget build(BuildContext context) {
  return ChangeNotifierProvider(
    create: (_) => Foo(),
    child: Consumer<Foo>(
      builder: (_, foo, __) => Text(foo.value),
    },
  );
}

本篇的內容就到此為止,因為內容篇多了一些,若再加上實作感覺會有點亂,因此實作的部份就留到明天。
有任何問題或是想說的話都歡迎留言及email,我們明天見!
email: nnyjan02426@gmail.com
github: nnyjan02426


上一篇
【Day 20】讓程式介面在新增項目時同步更新吧!
下一篇
【Day 22】ChangeNotifier與Consumer的實作應用
系列文
Flutter基礎入門30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言