1. 重走Flutter狀態(tài)管理之路—Riverpod入門篇

        共 21388字,需瀏覽 43分鐘

         ·

        2022-05-16 11:16

        點擊上方藍字關(guān)注我,知識會給你力量


        熟悉我的朋友應(yīng)該都知道,我好幾年前寫過一個「Flutter狀態(tài)管理之路」系列,那個時候介紹的是Provider,這也是官方推薦的狀態(tài)管理工具,但當時沒有寫完,因為寫著寫著,覺得有很多地方不盡人意,用著很別扭,所以在寫了7篇文章之后,就暫時擱置了。

        一晃時間過了這么久,F(xiàn)lutter內(nèi)部依然沒有一個能夠碾壓一切的狀態(tài)管理框架,GetX可能是,但是我覺得不是,InheritedWidget系的狀態(tài)管理,才應(yīng)該是正統(tǒng)的狀態(tài)管理。

        最近在留意Provider的后續(xù)進展時,意外發(fā)現(xiàn)了一個新的庫——Riverpod,號稱是新一代的狀態(tài)管理工具,仔細一看,嘿,居然還是Provider的作者,好家伙,這是搬起石頭砸自己的腳啊。

        就像作者所說,Riverpod就是對Provider的重寫,可不是嗎,字母都沒變,就換了個順序,這名字也是取的博大精深。

        其實Provider在使用上已經(jīng)非常不錯了,只不過隨著Flutter的更加深入,大家對它的需求也就越來越高,特別是對Provider中因為InheritedWidget層次問題導(dǎo)致的異常和BuildContext的使用這些問題詬病很多,而Riverpod,正是在Provider的基礎(chǔ)上,探索出了一條心的狀態(tài)管理之路。

        大家可以先把官方文檔看一看 https://riverpod.dev ,看完之后發(fā)現(xiàn)還是一臉懵逼,那就對了,Riverpod和Provider一樣,有很多類型的Provider,分別用于不同的場景,所以,理清這些Provider的不同作用和使用場景,對于我們用好Riverpod是非常有幫助的。

        官網(wǎng)的文檔,雖然是作者精心編寫的,但它的教程,站在的是一個創(chuàng)作者的角度,所以很多入門的初學(xué)者看上去會有點摸不清方向,所以,這才有了這個系列的文章。

        我將在這個系列中,帶領(lǐng)大家對文檔進行一次精讀,進行一次賞析,本文不全是對文檔的翻譯,而且講解的順序也不一樣,所以,如果你想入門Riverpod進行狀態(tài)管理,那么本文一定是你的最佳選擇。

        Provider第一眼

        首先,我們?yōu)槭裁匆M行狀態(tài)管理,狀態(tài)管理是解決申明式UI開發(fā),關(guān)于數(shù)據(jù)狀態(tài)的一個處理操作,例如Widget A依賴于同級的Widget B的數(shù)據(jù),那么這個時候,就只能把數(shù)據(jù)狀態(tài)上提到它們的父類,但是這樣比較麻煩,Riverpod和Provider這樣的狀態(tài)管理框架,就是為了解決類似的問題而產(chǎn)生的。

        將一個state包裹在一個Provider中可以有下面一些好處。

        • 允許在多個位置輕松訪問該狀態(tài)。Provider可以完全替代Singletons、Service Locators、依賴注入或InheritedWidgets等模式
        • 簡化了這個狀態(tài)與其他狀態(tài)的結(jié)合,你有沒有為,如何把多個對象合并成一個而苦惱過?這種場景可以直接在Provider內(nèi)部實現(xiàn)
        • 實現(xiàn)了性能優(yōu)化。無論是過濾Widget的重建,還是緩存昂貴的狀態(tài)計算;Provider確保只有受狀態(tài)變化影響的部分才被重新計算
        • 增加了你的應(yīng)用程序的可測試性。使用Provider,你不需要復(fù)雜的setUp/tearDown步驟。此外,任何Provider都可以被重寫,以便在測試期間有不同的行為,這可以輕松地測試一個非常具體的行為
        • 允許與高級功能輕松集成,如logging或pull-to-refresh

        首先,我們通過一個簡單的例子,來感受下,Riverpod是怎么進行狀態(tài)管理的。

        Provider是Riverpod應(yīng)用程序中最重要的部分。Provider是一個對象,它封裝了一個state并允許監(jiān)聽該state。Provider有很多變體形式,但它們的工作方式都是一樣的。

        最常見的用法是將它們聲明為全局常量,例如下面這樣。

        final myProvider = Provider((ref) {
          return MyValue();
        });
        ?

        不要被Provider的全局變量所嚇倒。Provider是完全final的。聲明一個Provider與聲明一個函數(shù)沒有什么不同,而且Provider是可測試和可維護的。

        ?

        這段代碼由三個部分組成。

        • final myProvider,一個變量的聲明。這個變量是我們將來用來讀取我們Provider的狀態(tài)的。Provider應(yīng)該始終是final的
        • Provider,我們決定使用的Provider類型。Provider是所有Provider類型中最基本的。它暴露了一個永不改變的對象。我們可以用其他Provider如StreamProvider或StateNotifierProvider來替換Provider,以改變值的交互方式
        • 一個創(chuàng)建共享狀態(tài)的函數(shù)。該函數(shù)將始終接收一個名為ref的對象作為參數(shù)。這個對象允許我們讀取其他Provider,在我們Provider的狀態(tài)將被銷毀時執(zhí)行一些操作,以及其它一些事情

        傳遞給Provider的函數(shù)返回的對象的類型,取決于所使用的Provider。例如,一個Provider的函數(shù)可以創(chuàng)建任何對象。另一方面,StreamProvider的回調(diào)將被期望返回一個Stream。

        ?

        你可以不受限制地聲明你想要的多個Provider。與使用package:provider不同的是,Riverpod允許創(chuàng)建多個暴露相同 "類型 "的狀態(tài)的provider。

        final cityProvider = Provider((ref) => 'London');
        final countryProvider = Provider((ref) => 'England');

        兩個Provider都創(chuàng)建了一個字符串,但這并沒有任何問題。

        ?

        為了使Provider發(fā)揮作用,您必須在Flutter應(yīng)用程序的根部添加ProviderScope。

        void main() {
          runApp(ProviderScope(child: MyApp()));
        }

        以上就是Riverpod最簡單的使用,我們看下完整的示例代碼。

        import 'package:flutter/material.dart';
        import 'package:flutter_riverpod/flutter_riverpod.dart';

        // We create a "provider"which will store a value (here "Hello world").
        // By using a provider, this allows us to mock/override the value exposed.
        final helloWorldProvider = Provider((_) => 'Hello world');

        void main() {
          runApp(
            // For widgets to be able to read providers, we need to wrap the entire
            // application in a "ProviderScope" widget.
            // This is where the state of our providers will be stored.
            ProviderScope(
              child: MyApp(),
            ),
          );
        }

        // Extend ConsumerWidget instead of StatelessWidget, which is exposed by Riverpod
        class MyApp extends ConsumerWidget {
          @override
          Widget build(BuildContext context, WidgetRef ref) {
            final String value = ref.watch(helloWorldProvider);

            return MaterialApp(
              home: Scaffold(
                appBar: AppBar(title: const Text('Example')),
                body: Center(
                  child: Text(value),
                ),
              ),
            );
          }
        }

        可以發(fā)現(xiàn),Riverpod的使用比package:Provider還要簡單,申明一個全局變量來管理狀態(tài)數(shù)據(jù),然后就可以在任意地方獲取數(shù)據(jù)了。

        如何讀取Provider的狀態(tài)值

        在有了一個簡單的了解后,我們先來了解下關(guān)于狀態(tài)中的「讀」。

        在Riverpod中,我們不像package:Provider那樣需要依賴BuildContext,取而代之的是一個「ref」變量。這個東西,就是聯(lián)系存取雙方的紐帶,這個對象允許我們與Provider互動,不管是來自一個Widget還是另一個Provider。

        從Provider中獲取ref

        所有Provider都有一個 "ref "作為參數(shù)。

        final provider = Provider((ref) {
          // use ref to obtain other providers
          final repository = ref.watch(repositoryProvider);

          return SomeValue(repository);
        })

        這個參數(shù)可以安全地傳遞給其它Provider或者類,來獲取所需要的值。

        例如,一個常見的用例是將Provider的 "ref "傳遞給一個StateNotifier。

        final counterProvider = StateNotifierProvider<Counter, int>((ref) {
          return Counter(ref);
        });

        class Counter extends StateNotifier<int> {
          Counter(this.ref): super(0);

          final Ref ref;

          void increment() {
            // Counter can use the "ref" to read other providers
            final repository = ref.read(repositoryProvider);
            repository.post('...');
          }
        }

        這樣做,可以使我們的Counter類能夠讀取Provider。

        ?

        這種方式是聯(lián)系組件和Provider的一個重要方式。

        ?

        從Widget中獲取ref

        Widgets自然沒有一個ref參數(shù)。但是Riverpod提供了多種解決方案來從widget中獲得這個參數(shù)。

        擴展ConsumerWidget

        在widget樹中獲得一個ref的最常見的方法是用ConsumerWidget代替StatelessWidget。

        ConsumerWidget在使用上與StatelessWidget相同,唯一的區(qū)別是它的構(gòu)建方法上有一個額外的參數(shù):"ref "對象。

        一個典型的ConsumerWidget看起來像這樣。

        class HomeView extends ConsumerWidget {
          const HomeView({Key? key}): super(key: key);

          @override
          Widget build(BuildContext context, WidgetRef ref) {
            // use ref to listen to a provider
            final counter = ref.watch(counterProvider);
            return Text('$counter');
          }
        }
        擴展ConsumerStatefulWidget

        與ConsumerWidget類似,ConsumerStatefulWidget和ConsumerState相當于一個帶有狀態(tài)的StatefulWidget,不同的是,state有一個 "ref "對象。

        這一次,"ref "不是作為構(gòu)建方法的參數(shù)傳遞,而是作為ConsumerState對象的一個屬性。

        class HomeView extends ConsumerStatefulWidget {
          const HomeView({Key? key}): super(key: key);

          @override
          HomeViewState createState() => HomeViewState();
        }

        class HomeViewState extends ConsumerState<HomeView> {
          @override
          void initState() {
            super.initState();
            // "ref" can be used in all life-cycles of a StatefulWidget.
            ref.read(counterProvider);
          }

          @override
          Widget build(BuildContext context) {
            // We can also use "ref" to listen to a provider inside the build method
            final counter = ref.watch(counterProvider);
            return Text('$counter');
          }
        }

        通過ref來獲取狀態(tài)

        現(xiàn)在我們有了一個 "ref",我們可以開始使用它。

        ref "有三個主要用途。

        • 獲得一個Provider的值并監(jiān)聽變化,這樣,當這個值發(fā)生變化時,這將重建訂閱該值的Widget或Provider。這是通過ref.watch完成的
        • 在一個Provider上添加一個監(jiān)聽器,以執(zhí)行一個action,如導(dǎo)航到一個新的頁面或在該Provider發(fā)生變化時執(zhí)行一些操作。這是通過 ref.listen 完成的
        • 獲取一個Provider的值,同時忽略它的變化。當我們在一個事件中需要一個Provider的值時,這很有用,比如 "點擊操作"。這是通過ref.read完成的
        ?

        只要有可能,最好使用 ref.watch 而不是 ref.read 或 ref.listen 來實現(xiàn)一個功能。通過依賴ref.watch,你的應(yīng)用程序變得既是反應(yīng)式的又是聲明式的,這使得它更容易維護。

        ?

        通過ref.watch觀察Provider的狀態(tài)

        ref.watch在Widget的構(gòu)建方法中使用,或者在Provider的主體中使用,以使得Widget/Provider可以監(jiān)聽另一個Provider。

        例如,Provider可以使用 ref.watch 來將多個Provider合并成一個新的值。

        一個例子是過濾一個todo-list,我們需要兩個Provider。

        • filterTypeProvider,一個暴露當前過濾器類型的Provider(None,表示只顯示已完成的任務(wù))
        • todosProvider,一個暴露整個任務(wù)列表的Provider

        通過使用ref.watch,我們可以制作第三個Provider,結(jié)合這兩個Provider來創(chuàng)建一個過濾后的任務(wù)列表。

        final filterTypeProvider = StateProvider<FilterType>((ref) => FilterType.none);
        final todosProvider = StateNotifierProvider<TodoList, List<Todo>>((ref) => TodoList());

        final filteredTodoListProvider = Provider((ref) {
          // obtains both the filter and the list of todos
          final FilterType filter = ref.watch(filterTypeProvider);
          final List<Todo> todos = ref.watch(todosProvider);

          switch (filter) {
            case FilterType.completed:
              // return the completed list of todos
              return todos.where((todo) => todo.isCompleted).toList();
            case FilterType.none:
              // returns the unfiltered list of todos
              return todos;
          }
        });

        有了這段代碼,filteredTodoListProvider現(xiàn)在就可以管理過濾后的任務(wù)列表。

        如果過濾器或任務(wù)列表發(fā)生變化,過濾后的列表也會自動更新。同時,如果過濾器和任務(wù)列表都沒有改變,過濾后的列表將不會被重新計算。

        類似地,一個Widget可以使用ref.watch來顯示來自Provider的內(nèi)容,并在該內(nèi)容發(fā)生變化時更新用戶界面。

        final counterProvider = StateProvider((ref) => 0);

        class HomeView extends ConsumerWidget {
          const HomeView({Key? key}): super(key: key);

          @override
          Widget build(BuildContext context, WidgetRef ref) {
            // use ref to listen to a provider
            final counter = ref.watch(counterProvider);

            return Text('$counter');
          }
        }

        這段代碼顯示了一個Widget,它監(jiān)聽了一個存儲計數(shù)的Provider。如果該計數(shù)發(fā)生變化,該Widget將重建,用戶界面將更新以顯示新的值。

        ?

        ref.watch方法不應(yīng)該被異步調(diào)用,比如在ElevatedButton的onPressed中。也不應(yīng)該在initState和其他State的生命周期內(nèi)使用它。在這些情況下,考慮使用 ref.read 來代替。

        ?

        通過ref.listen監(jiān)聽Provider的變化

        與ref.watch類似,可以使用ref.listen來觀察一個Provider。

        它們之間的主要區(qū)別是,如果被監(jiān)聽的Provider發(fā)生變化,使用ref.listen不會重建widget/provider,而是會調(diào)用一個自定義函數(shù)。

        這對于在某個變化發(fā)生時執(zhí)行某些操作是很有用的,比如在發(fā)生錯誤時顯示一個snackbar。

        ref.listen方法需要2個參數(shù),第一個是Provider,第二個是當狀態(tài)改變時我們要執(zhí)行的回調(diào)函數(shù)。回調(diào)函數(shù)在被調(diào)用時將被傳遞2個值,即先前狀態(tài)的值和新狀態(tài)的值。

        ref.listen方法也可以在Provider的體內(nèi)使用。

        final counterProvider = StateNotifierProvider<Counter, int>((ref) => Counter(ref));

        final anotherProvider = Provider((ref) {
          ref.listen<int>(counterProvider, (int? previousCount, int newCount) {
            print('The counter changed $newCount');
          });
          // ...
        });

        或在一個Widget的Build方法中使用。

        final counterProvider = StateNotifierProvider<Counter, int>((ref) => Counter(ref));

        class HomeView extends ConsumerWidget {
          const HomeView({Key? key}): super(key: key);

          @override
          Widget build(BuildContext context, WidgetRef ref) {
            ref.listen<int>(counterProvider, (int? previousCount, int newCount) {
              print('The counter changed $newCount');
            });
            
            return Container();
          }
        }
        ?

        ref.listen也不應(yīng)該被異步調(diào)用,比如在ElevatedButton的onPressed中。也不應(yīng)該在initState和其他State的生命周期內(nèi)使用它。

        ?

        通過ref.read來讀取Provider的狀態(tài)

        ref.read方法是一種在不監(jiān)聽的情況下獲取Provider的狀態(tài)的方法。

        它通常用于由用戶交互觸發(fā)的函數(shù)中。例如,當用戶點擊一個按鈕時,我們可以使用ref.read來增加一個計數(shù)器的值。

        final counterProvider = StateNotifierProvider<Counter, int>((ref) => Counter(ref));

        class HomeView extends ConsumerWidget {
          const HomeView({Key? key}): super(key: key);

          @override
          Widget build(BuildContext context, WidgetRef ref) {
            return Scaffold(
              floatingActionButton: FloatingActionButton(
                onPressed: () {
                  // Call `increment()` on the `Counter` class
                  ref.read(counterProvider.notifier).increment();
                },
              ),
            );
          }
        }
        ?

        應(yīng)該盡可能地避免使用ref.read,因為它不是響應(yīng)式的。

        它存在于使用watch或listen會導(dǎo)致問題的情況下。如果可以的話,使用watch/listen幾乎總是更好的,尤其是watch。

        ?

        關(guān)于ref.read到底什么時候用

        首先,永遠不要在Widget的build函數(shù)中直接使用ref.read。

        你可能很想使用ref.read來優(yōu)化一個Widget的性能,例如通過下面的代碼來實現(xiàn)。

        final counterProvider = StateProvider((ref) => 0);

        Widget build(BuildContext context, WidgetRef ref) {
          // use "read" to ignore updates on a provider
          final counter = ref.read(counterProvider.notifier);
          return ElevatedButton(
            onPressed: () => counter.state++,
            child: const Text('button'),
          );
        }

        但這是一種非常糟糕的做法,會導(dǎo)致難以追蹤的錯誤。

        以這種方式使用 ref.read 通常與這樣的想法有關(guān):"Provider所暴露的值永遠不會改變,所以使用'ref.read'是安全的"。這個假設(shè)的問題是,雖然今天該Provider可能確實從未更新過它的值,但不能保證明天也是如此。

        軟件往往變化很大,而且很可能在未來,一個以前從未改變的值需要改變。

        如果你使用ref.read,當這個值需要改變時,你必須翻閱整個代碼庫,將ref.read改為ref.watch--這很容易出錯,而且你很可能會忘記一些情況。

        如果你一開始就使用ref.watch,你在重構(gòu)時就會減少問題。

        但是如果我想用ref.read來減少我的widget重構(gòu)的次數(shù)呢?

        雖然這個目標值得稱贊,但需要注意的是,你可以用ref.watch代替來達到完全相同的效果(減少構(gòu)建的次數(shù))。

        Provider提供了各種方法來獲得一個值,同時減少重建的次數(shù),你可以用這些方法來代替。

        例如下面的代碼(bad)。

        final counterProvider = StateProvider((ref) => 0);

        Widget build(BuildContext context, WidgetRef ref) {
          StateController<int> counter = ref.read(counterProvider.notifier);
          return ElevatedButton(
            onPressed: () => counter.state++,
            child: const Text('button'),
          );
        }

        我們可以這樣改。

        final counterProvider = StateProvider((ref) => 0);

        Widget build(BuildContext context, WidgetRef ref) {
          StateController<int> counter = ref.watch(counterProvider.notifier);
          return ElevatedButton(
            onPressed: () => counter.state++,
            child: const Text('button'),
          );
        }

        這兩個片段代碼都達到了同樣的效果:當計數(shù)器增加時,我們的按鈕將不會重建。

        另一方面,第二種方法支持計數(shù)器被重置的情況。例如,應(yīng)用程序的另一部分可以調(diào)用。

        ref.refresh(counterProvider);

        這將重新創(chuàng)建StateController對象。

        如果我們在這里使用ref.read,我們的按鈕仍然會使用之前的StateController實例,而這個實例已經(jīng)被棄置,不應(yīng)該再被使用。

        而使用ref.watch則可以正確地重建按鈕,使用新的StateController。

        關(guān)于ref.read可以讀哪些值

        根據(jù)你想監(jiān)聽的Provider,你可能有多個可能的值可以監(jiān)聽。

        作為一個例子,考慮下面的StreamProvider。

        final userProvider = StreamProvider<User>(...);

        當讀取這個userProvider時,你可以像下面這樣。

        • 通過監(jiān)聽userProvider本身同步讀取當前狀態(tài)。
        Widget build(BuildContext context, WidgetRef ref) {
          AsyncValue<User> user = ref.watch(userProvider);

          return user.when(
            loading: () => const CircularProgressIndicator(),
            error: (error, stack) => const Text('Oops'),
            data: (user) => Text(user.name),
          );
        }
        • 通過監(jiān)聽userProvider.stream來獲得相關(guān)的Stream。
        Widget build(BuildContext context, WidgetRef ref) {
          Stream<User> user = ref.watch(userProvider.stream);
        }
        • 通過監(jiān)聽userProvider.future獲得一個Future,該Future以最新發(fā)出的值進行解析。
        Widget build(BuildContext context, WidgetRef ref) {
          Future<User> user = ref.watch(userProvider.future);
        }

        其他Provider可能提供不同的替代值。

        欲了解更多信息,請查閱API參考資料,參考每個Provider的API文檔。

        通過select來控制精確的讀范圍

        最后要提到的一個與讀取Provider有關(guān)的功能是,能夠減少Widget/Provider從ref.watch重建的次數(shù),或者ref.listen執(zhí)行函數(shù)的頻率的功能。

        這一點很重要,因為默認情況下,監(jiān)聽一個Provider會監(jiān)聽整個對象的狀態(tài)。但有時,一個Widget/Provider可能只關(guān)心一些屬性的變化,而不是整個對象。

        例如,一個Provider可能暴露了一個User對象。

        abstract class User {
          String get name;
          int get age;
        }

        但一個Widget可能只使用用戶名。

        Widget build(BuildContext context, WidgetRef ref) {
          User user = ref.watch(userProvider);
          return Text(user.name);
        }

        如果我們簡單地使用ref.watch,當用戶的年齡發(fā)生變化時,這將重建widget。

        解決方案是使用select來明確地告訴Riverpod我們只想監(jiān)聽用戶的名字屬性。

        更新后的代碼將是這樣。

        Widget build(BuildContext context, WidgetRef ref) {
          String name = ref.watch(userProvider.select((user) => user.name));
          return Text(name);
        }

        通過使用select,我們能夠指定一個函數(shù)來返回我們關(guān)心的屬性。

        每當用戶改變時,Riverpod將調(diào)用這個函數(shù)并比較之前和新的結(jié)果。如果它們是不同的(例如當名字改變時),Riverpod將重建Widget。然而,如果它們是相等的(例如當年齡改變時),Riverpod將不會重建Widget。

        ?

        這個場景也可以使用select和ref.listen。

        ref.listen<String>(
              userProvider.select((user) => user.name),
                  (String? previousName, String newName) {
                 print('The user name changed $newName');
              }
          );

        這樣做也將只在名稱改變時調(diào)用listener。

        另外,你不一定要返回對象的一個屬性。任何覆蓋==的值都可以使用。例如,你可以這樣做。

        final label = ref.watch(userProvider.select((user) => 'Mr ${user.name}'));
        ?

        讀取狀態(tài),是一個非常重要的部分,什么時候用什么樣的方式來讀,都會有不同的效果。

        ProviderObserver

        ProviderObserver可以監(jiān)聽一個ProviderContainer的變化。

        要使用它,你可以擴展ProviderObserver類并覆蓋你想使用的方法。ProviderObserver有三個方法。

        • didAddProvider:在每次初始化一個Provider時被調(diào)用
        • didDisposeProvider:在每次銷毀Provider的時候被調(diào)用
        • didUpdateProvider:每次在Provider更新時都會被調(diào)用

        ProviderObserver的一個簡單用例是通過覆蓋didUpdateProvider方法來記錄Provider的變化。

        // A Counter example implemented with riverpod with Logger

        class Logger extends ProviderObserver {
          @override
          void didUpdateProvider(
            ProviderBase provider,
            Object? previousValue,
            Object? newValue,
            ProviderContainer container,
          ) {
            print('''
        {
          "provider": "${provider.name ?? provider.runtimeType}",
          "newValue": "$newValue"
        }'
        '');
          }
        }

        void main() {
          runApp(
            // Adding ProviderScope enables Riverpod for the entire project
            // Adding our Logger to the list of observers
            ProviderScope(observers: [Logger()], child: const MyApp()),
          );
        }

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

          @override
          Widget build(BuildContext context) {
            return MaterialApp(home: Home());
          }
        }

        final counterProvider = StateProvider((ref) => 0, name: 'counter');

        class Home extends ConsumerWidget {
          @override
          Widget build(BuildContext context, WidgetRef ref) {
            final count = ref.watch(counterProvider);

            return Scaffold(
              appBar: AppBar(title: const Text('Counter example')),
              body: Center(
                child: Text('$count'),
              ),
              floatingActionButton: FloatingActionButton(
                onPressed: () => ref.read(counterProvider.notifier).state++,
                child: const Icon(Icons.add),
              ),
            );
          }
        }

        現(xiàn)在,每當我們的Provider的值被更新時,logger將記錄它。

        I/flutter (16783): {
        I/flutter (16783):   "provider""counter",
        I/flutter (16783):   "newValue""1"
        I/flutter (16783): }
        ?

        對于諸如StateController(StateProvider.state的狀態(tài))和ChangeNotifier等可改變的狀態(tài),previousValue和newValue將是相同的。因為它們引用的是同一個StateController / ChangeNotifier。

        ?

        這些是對Riverpod的最基本了解,但是卻是很重要的部分,特別是如何對狀態(tài)值進行讀取,這是我們用好Riverpod的核心。

        向大家推薦下我的網(wǎng)站 https://xuyisheng.top/  點擊原文一鍵直達

        專注 Android-Kotlin-Flutter 歡迎大家訪問



        往期推薦


        本文原創(chuàng)公眾號:群英傳,授權(quán)轉(zhuǎn)載請聯(lián)系微信(Tomcat_xu),授權(quán)后,請在原創(chuàng)發(fā)表24小時后轉(zhuǎn)載。
        < END >
        作者:徐宜生

        更文不易,點個“三連”支持一下??


        瀏覽 77
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
          
          

            1. 涩涩视频在线播放 | 欧美日韩国产一区 | 夜里十大禁用αpp软件俏佳人 | 美女又爽又黄视频毛茸茸 | 国产黄色片网站 |