在前端系統中,“狀態管理”非常重要。
之前介紹過的「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時需要準備以下幾點。
- 狀態類
- Notifier
- 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有所幫助。
感謝您閱讀到最後!!