什么是 Riverpod,Flutter 最重要的状态管理介绍!

在前端系統中,“狀態管理”非常重要。

之前介紹過的「StatefulWidget」也是進行狀態管理的功能,
但如果要實現擁有多個畫面或功能的應用程式,管理的狀態會增加,管理變得困難。

這時,有一個能簡單管理狀態的優秀套件Riverpod。
這次,我們將介紹Riverpod的概要和基本使用方法。
這裡介紹的Riverpod是v2版本。

什麼是Riverpod

Riverpod是一種用於定義全域狀態(State)的工具。

如果是變數或常數,通過改變定義的範圍,可以相對簡單地定義全域變數。

然而,前端系統中使用的狀態(State)與Widget有著密切關聯,
由於狀態變化會重新構建Widget,因此並不那麼簡單。
此外,如果要在多個Widget中參考相同的狀態信息,會變得更加複雜。

這時候能簡單實現這種需求的工具就是Riverpod。

Riverpod不僅僅是一個用於全域變數的工具,
但這次只做基本介紹。

關於狀態管理

在介紹Riverpod之前,先介紹一下狀態管理。

狀態(State)

簡單來說,狀態(State)是指影響畫面信息的變數。

例如,登錄的用戶信息、ToDo列表的信息、
選中或未選中的複選框等,這些影響畫面顯示的數據就是狀態(State)

Widget監視這些狀態,每當有變化時就重新構建畫面。

狀態管理

適當更新或刪除這些狀態並在畫面上呈現,這就是狀態管理。
狀態有本地狀態和全域狀態。

  • 本地狀態
    僅用於一個畫面或一個Widget。
    僅在該畫面使用的文本框或計數器的值等。
  • 全域狀態
    應用程式整體共享的狀態。
    顯示在主頁面或用戶信息頁面的用戶信息等。

Flutter中有狀態管理的”StatefulWidget”。
然而,如果要用”StatefulWidget”管理全域狀態會變得複雜。

當狀態需要傳遞到其他畫面或在其他畫面更新時,
需要實現如同步等功能。

在Riverpod中,Provider監視狀態並自動傳遞給受影響的Widget,
因此無需實現上述同步功能。

關於Riverpod

Riverpod是一個為了簡化Flutter中狀態管理而設計的開源套件。
通過以下功能進行狀態管理。

  • Notifier
    Notifier(通知者)管理狀態和更新刪除的邏輯。
    負責更新狀態和通知Widget狀態變化。
  • Provider
    Widget通過Provider(提供者)參考或更新Notifier(狀態)的值。

使用Riverpod,像上面的圖示一樣,WidgetA的更新也會傳遞給WidgetB!

Riverpod的使用方法(概要)

使用Riverpod時需要準備以下幾點。

  1. 狀態類
  2. Notifier
  3. Provider

對於「3. Provider」,在v2.0以後,“Riverpod Generator”會自動生成。

Riverpod的基本使用方法

以ToDo應用程式為例介紹基本使用方法。

設定檔

在pubspec.yaml中添加以下套件。

dependencies:
  flutter_riverpod:
  riverpod_annotation:
  freezed_annotation:

dev_dependencies:
  build_runner:
  riverpod_generator:
  

Riverpod相關套件
flutter_riverpod:在Flutter中使用Riverpod的套件
riverpod_generator:自動定義Riverpod的Provider的套件
riverpod_annotation:用于riverpod_generator的註解識別套件

其他套件
build_runner:進行實際代碼自動生成的套件
freezed:用於創建不可變類的套件
freezed_annotation:用于freezed的註解識別套件

狀態類的定義

作為狀態使用的類需要是不可變(Immutable)類。
以下示例使用freezed等定義不可變類。

import 'package:freezed_annotation/freezed_annotation.dart';

part 'todo.freezed.dart';

@freezed // 使用freezed的註解
class Todo with _$Todo {
  factory Todo({
    required String id,
    required String description,
    required bool completed,
  }) = _Todo;
}

不可變的優勢是什麼?

不可變類是一個無法更新值的類。
也就是說,一旦創建就無法更改值。聽起來似乎不方便。

非不可變類的實例數據傳遞是“引用傳遞”。
當傳遞實例給其他類或函數時,不是傳遞值本身,
而是傳遞包含值的地址。

如果更改傳遞給類或函數的實例的值,
引用該地址的調用方的實例的值也會更改。

僅此而已,似乎可以用於狀態管理,
但引用傳遞的麻煩之處在於,即使是用於讀取的實例,
也可以更新地址值,可能會導致未預期的更新。
此外,這種更新不會產生錯誤,因此無法檢測到。

這種未預期的更新,例如在排序複製的實例時,
複製源也會同樣排序。

在Riverpod中,為了阻止上述未預期的變更,
希望用不可變類進行狀態管理,以便只能在有意圖時進行更新。

定義Notifier

基於上面定義的類定義Notifier。
這裡描述了更新狀態和新建的處理。

import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'todo.dart';

// 以下步驟執行build runner後生成此文件
part 'todo_notifier.g.dart';

@riverpod
class TodoNotifier extends _$TodoNotifier {
  @override
  List<Todo> build() {
    return [];
  }

  // 新增
  void addTodo(Todo todo) {
    state = [...state, todo];
  }

  // 刪除
  void removeTodo(String todoId) {
    state = [
      for (final todo in state)
        if (todo.id != todoId) todo,
    ];
  }

  // 切換"completed"的判定
  void toggle(String todoId) {
    state = [
      for (final todo in state)
        if (todo.id == todoId)
          todo.copyWith(completed: !todo.completed)
        else
          todo,
    ];
  }
}

本文旨在介紹Riverpod的概要,因此省略了具體處理的說明。

運行Riverpod Generator

在終端中運行以下命令來執行build_runner。

flutter pub run build_runner build --delete-conflicting-outputs

這樣會啟動Riverpod Generator,生成〜.d.dart文件。
由於該命令也會啟動freezed,會生成~.freezed.dart文件。

通過Riverpod Generator,會生成Provider。
查看生成的〜.d.dart文件會發現Provider已被定義。

// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'todo_notifier.dart';

// **************************************************************************
// RiverpodGenerator
// **************************************************************************

String _$todoNotifierHash() => r'1330ee68f148d9ff18b1e0370e3f3541ab67c621';

@ProviderFor(TodoNotifier)
final todoNotifierProvider =
    AutoDisposeNotifierProvider<TodoNotifier, List<Todo>>.internal(
  TodoNotifier.new,
  name: r'todoNotifierProvider',
  debugGetCreateSourceHash:
      const bool.fromEnvironment('dart.vm.product') ? null : _$todoNotifierHash,
  dependencies: null,
  allTransitiveDependencies: null,
);

typedef _$TodoNotifier = AutoDisposeNotifier<List<Todo>>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member

使用Riverpod的Widget

要使用Riverpod,需要聲明使用範圍。

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

void main() {
  // 使用Riverpod的範圍聲明為"ProviderScope"
  runApp(ProviderScope(child: MyApp()));
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Material

App(
      home: TodoItemListPage(),
    );
  }
}

// 使用"ConsumerWidget"定義使用Riverpod的Widget
class TodoItemListPage extends ConsumerWidget {
  @override
  // "WidgetRef ref"是使用Provider的上下文
  Widget build(BuildContext context, WidgetRef ref) {
    // "ref.watch"監視Provider,檢測狀態變更。
    List<TodoItem> todoItems = ref.watch(todoNotifierProvider);

    return Scaffold(
      appBar: AppBar(title: Text('todoItem List')),
      body: ListView.builder(
        itemCount: todoItems.length,
        itemBuilder: (context, index) {
          final todoItem = todoItems[index];
          return ListTile(
            title: Text(todoItem.title),
            leading: Checkbox(
              value: todoItem.completed,
              onChanged: (bool? newValue) {
                // "ref.read"從Provider調用Notifier的方法。
                ref.read(todoNotifierProvider.notifier).toggle(todoItem.id);
              },
            ),
            onLongPress: () => ref.read(todoNotifierProvider.notifier).removeTodo(todoItem.id),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // addTodo
        },
        child: Icon(Icons.add),
      ),
    );
  }
}

“ref”是reference(參考)的縮寫。
從Widget到狀態的參考是,ref→Provider→Notifier→State。

※在上述處理中,沒有使用Notifier的”addTodo”。

運行畫面

運行後,初始數據會顯示如下。
點擊複選框或長按列表項目,狀態會發生變化。

完成!!

最後

這次介紹了Riverpod的基本內容。
Riverpod有許多功能,希望將來能介紹這些功能。

希望本文能對您理解Riverpod有所幫助。
感謝您閱讀到最後!!

标题和URL已复制