使用Riverpod在Flutter中創(chuàng)建Todo列表

使用Riverpod在Flutter中創(chuàng)建Todo列表

視頻

https://youtu.be/mlbeSD1KSIo

https://www.bilibili.com/video/BV1jj42197c8/

前言

原文 https://ducafecat.com/blog/flutter-todo-list-with-riverpod-guide-02

學習如何使用Riverpod在Flutter中構建一個功能完整的Todo列表應用梗肝。通過Consumer組件、ConsumerStatefulWidget類铺董、ref.read方法和provider build重寫巫击,了解Riverpod的狀態(tài)管理和更新狀態(tài)機制。

參考

https://pub.dev/packages/riverpod

https://riverpod.dev/

https://flutter.ducafecat.com/

知識點

  1. Consumer 組件:
    Consumer組件是Riverpod提供的用于訂閱和監(jiān)聽Provider數據變化的組件精续。它接收一個或多個Provider坝锰,并在數據發(fā)生變化時重新構建其子組件。
  2. ConsumerStatefulWidget 類:
    ConsumerStatefulWidget是一個抽象類重付,繼承自StatefulWidget顷级。通過繼承ConsumerStatefulWidget類并實現其createState方法,可以創(chuàng)建一個具有可變狀態(tài)和自動重建機制的組件确垫。
  3. ref.read 方法:
    ref.read方法用于從ProviderContainer中獲取Provider的值弓颈,而無需訂閱或監(jiān)聽它。它適用于在不需要實時更新的情況下獲取Provider的當前值删掀。
  4. provider build 重寫:
    通過重寫Provider的build方法翔冀,可以在Provider的值發(fā)生變化時重新構建其依賴項。這使得我們可以控制在數據變化時如何更新UI披泪。
  5. update state 更新狀態(tài):
    在Riverpod中纤子,狀態(tài)更新是通過修改可變的Provider值或使用 invalidateSelf 來完成,觸發(fā)UI的重建付呕。

步驟

實例對象 TodoEntity

lib/entity/todo.dart

class TodoEntity {
  final int? id;
  final String? title;
  final String? description;
  final bool? completed;

  TodoEntity({this.id, this.title, this.description, this.completed});

  Map<String, dynamic> toMap() {
    return {
      'id': id,
      'title': title,
      'description': description,
      'completed': completed,
    };
  }

  static TodoEntity fromMap(Map<String, dynamic> map) {
    return TodoEntity(
      id: map['id'],
      title: map['title'],
      description: map['description'],
      completed: map['completed'],
    );
  }
}

實現 todo provider

lib/provider/todo_list.dart

定義 TodoList provider 類计福,todo 數據集合

part 'todo_list.g.dart';

@riverpod
class TodoList extends _$TodoList {
  static List<TodoEntity> items = [];

  @override
  Future<List<TodoEntity>> build() async {
    return items;
  }
  
  ...

實現新增 add 功能

  Future<void> addTodo(TodoEntity todo) async {
    // await http.post(
    //   Uri.https('your_api.com', '/todos'),
    //   // 我們序列化 Todo 對象并將其 POST 到服務器。
    //   headers: {'Content-Type': 'application/json'},
    //   body: jsonEncode(todo.toJson()),
    // );

    // 延遲 1 秒
    await Future.delayed(const Duration(seconds: 1));

    // 我們將新的 Todo 添加到 items 列表中徽职。
    items.add(todo);
  }

Consumer 新增 todo 按鈕欄

lib/pages/todo/widgets/bar.dart

TodoBarWidget 類

class TodoBarWidget extends StatelessWidget {
  const TodoBarWidget({super.key});

Consumer 方式實現關聯 riverpod

  Widget _buildView() {
    // Consumer 提供監(jiān)聽功能的小部件
    return Consumer(builder: (
      BuildContext context,
      WidgetRef ref,
      Widget? widget,
    ) {
      return Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          ElevatedButton(
            onPressed: () {
              // 事件處理用 read 方式讀取 provider
              ref.read(todoListProvider.notifier).addTodo(
                    TodoEntity(
                      description: 'This is a new todo',
                    ),
                  );
            },
            child: const Text('Add todo'),
          ),
        ],
      );
    });
  }

注意使用 ref.read象颖,雖然 ref.watch 也能用

  @override
  Widget build(BuildContext context) {
    return _buildView();
  }

狀態(tài)更新兩種方式

lib/provider/todo_list.dart

直接設置新狀態(tài)值

  Future<void> addTodo(TodoEntity todo) async {
    ...
    
    // 直接設置 state 值
    state = AsyncData(items);
  }

將本地緩存標記為臟

  Future<void> addTodo(TodoEntity todo) async {
    ...
     
    // 將本地緩存標記為臟
    ref.invalidateSelf();
    // 重新構建 TodoList, 呼叫 build 方法
    await future;
  }

ConsumerWidget 監(jiān)聽 todo 列表

lib/pages/todo/widgets/list.dart

TodoListWidget 類 繼承 ConsumerWidget

class TodoListWidget extends ConsumerWidget {
  const TodoListWidget({super.key});

監(jiān)聽 todo 列表

  Widget _buildList(ref) {
    var todos = ref.watch(todoListProvider);
    return switch (todos) {
      AsyncData<List<TodoEntity>>(:final value) => ListView.builder(
          itemCount: value.length,
          itemBuilder: (context, index) {
            return ListTile(
              title: Text(value[index].description ?? ""),
              subtitle: Text((value[index].completed ?? false) ? "yes" : "no"),
            );
          },
        ),
      AsyncError() => const Text('Oops, something unexpected happened'),
      _ => const CircularProgressIndicator(),
    };
  }
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return _buildList(ref);
  }

build 回調會有一個 ref 對象,直接傳入 _buildList 使用

todo 主界面實現

lib/pages/todo/index.dart

TodoPage 類姆钉,使用 StatelessWidget 就行

class TodoPage extends StatelessWidget {
  const TodoPage({super.key});

界面 build

  Widget _buildView(BuildContext context) {
    return const Center(
      child: Column(
        children: <Widget>[
          // StatelessWidget 的組件
          TodoBarWidget(),

          // StatefulWidget 的組件
          // TodoBarStfWidget(),

          // ConsumerWidget 的列表
          Expanded(
            child: TodoListWidget(),
          ),
        ],
      ),
    );
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Todo List')),
      body: _buildView(context),
    );
  }

加入啟動菜單

lib/pages/index.dart

  Widget _buildView(BuildContext context) {
    return Center(
      ...
      
          _buildBtn(context, '03 TODO 列表', const TodoPage()),
        ],
      ),
    );
  }

ConsumerStatefulWidget 界面 UI 交互

編寫 TodoBarStfWidget 繼承 ConsumerStatefulWidget

可以用插件 Flutter Riverpod Snippets 簡化輸入代碼塊

https://marketplace.visualstudio.com/items?itemName=robert-brunhage.flutter-riverpod-snippets

class TodoBarStfWidget extends ConsumerStatefulWidget {
  const TodoBarStfWidget({super.key});

  @override
  ConsumerState<ConsumerStatefulWidget> createState() =>
      _TodoBarStfWidgetState();
}

class _TodoBarStfWidgetState extends ConsumerState<TodoBarStfWidget> {

定義 Future 狀態(tài)對象監(jiān)聽變化

  // 待處理的 addTodo 操作说订。如果沒有待處理的,則為 null潮瓶。
  Future<void>? _pendingAddTodo;

通過 FutureBuilder 更新 UI


  Widget _buildView() {
    return FutureBuilder(
      // 我們監(jiān)聽待處理的操作陶冷,以相應地更新 UI。
      future: _pendingAddTodo,
      builder: (context, snapshot) {
        // 計算是否存在錯誤狀態(tài)毯辅。
        // 檢查 connectionState 用于在重試操作時進行處理埂伦。
        // 是否錯誤
        final isErrored = snapshot.hasError &&
            snapshot.connectionState != ConnectionState.waiting;
        // 是否等待
        final isWaiting = snapshot.connectionState == ConnectionState.waiting;

        return Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // 按鈕
            ElevatedButton(
              style: ButtonStyle(
                // 如果出現錯誤,我們會將該按鈕顯示為紅色
                backgroundColor: MaterialStateProperty.all(
                  isErrored ? Colors.red : null,
                ),
              ),
              // 設置 null 后按鈕灰色禁止點擊
              onPressed: isWaiting == true
                  ? null
                  : () {
                      // 我們將 addTodo 返回的 future 保存在變量中
                      final future = ref
                          .read(todoListProvider.notifier)
                          .addTodo(
                              TodoEntity(description: 'This is a new todo'));

                      // 我們將這個 future 存儲在本地的狀態(tài)中
                      setState(() {
                        _pendingAddTodo = future;
                      });
                    },
              child: const Text('Add todo V2'),
            ),

            // 操作正在等待思恐,讓我們顯示一個進度指示器
            if (isWaiting) ...[
              const SizedBox(width: 8),
              const CircularProgressIndicator(),
            ]
          ],
        );
      },
    );
  }
  @override
  Widget build(BuildContext context) {
    return _buildView();
  }

代碼

https://github.com/ducafecat/flutter_develop_tips/tree/main/flutter_application_riverpod

小結

本文介紹了如何使用Riverpod在Flutter中實現一個Todo列表功能沾谜。通過Consumer組件和ConsumerStatefulWidget類膊毁,我們可以訂閱和監(jiān)聽數據變化,并在需要時更新UI基跑。使用ref.read方法可以獲取Provider的值婚温,從而實現數據的讀取和操作。通過provider build重寫媳否,我們可以在數據變化時重新構建UI栅螟。這篇文章幫助讀者更深入地了解了如何使用Riverpod進行狀態(tài)管理,并實現了一個基本的Todo列表應用篱竭。

感謝閱讀本文

如果有什么建議力图,請在評論中讓我知道。我很樂意改進室抽。


? 貓哥
ducafecat.com

end

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末搪哪,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子坪圾,更是在濱河造成了極大的恐慌晓折,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件兽泄,死亡現場離奇詭異漓概,居然都是意外死亡,警方通過查閱死者的電腦和手機病梢,發(fā)現死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進店門胃珍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蜓陌,你說我怎么就攤上這事觅彰。” “怎么了钮热?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵填抬,是天一觀的道長。 經常有香客問我隧期,道長飒责,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任仆潮,我火速辦了婚禮宏蛉,結果婚禮上,老公的妹妹穿的比我還像新娘性置。我一直安慰自己拾并,他們只是感情好,可當我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著嗅义,像睡著了一般个榕。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上芥喇,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天,我揣著相機與錄音凰萨,去河邊找鬼继控。 笑死,一個胖子當著我的面吹牛胖眷,可吹牛的內容都是我干的武通。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼珊搀,長吁一口氣:“原來是場噩夢啊……” “哼冶忱!你這毒婦竟也來了?” 一聲冷哼從身側響起境析,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤囚枪,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后劳淆,有當地人在樹林里發(fā)現了一具尸體链沼,經...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年沛鸵,在試婚紗的時候發(fā)現自己被綠了括勺。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡曲掰,死狀恐怖疾捍,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情栏妖,我是刑警寧澤乱豆,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站底哥,受9級特大地震影響咙鞍,放射性物質發(fā)生泄漏。R本人自食惡果不足惜趾徽,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一续滋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧孵奶,春花似錦疲酌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽湿颅。三九已至,卻和暖如春粥诫,著一層夾襖步出監(jiān)牢的瞬間油航,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工怀浆, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留谊囚,地道東北人。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓执赡,卻偏偏與公主長得像镰踏,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子沙合,可洞房花燭夜當晚...
    茶點故事閱讀 44,573評論 2 353

推薦閱讀更多精彩內容