Riverpod.dev/docs(開始URL: https://riverpod.dev/docs/introduction/why_riverpod )には日本語訳ページ(/ja)も部分的には既にあるのですが、その部分はそのまま参照して、その他の英語訳のままのページについては自力で翻訳および解説を補足してみました。まだ工事中ですが最終的に満足ができたら上記サイトの翻訳ページに献上できないかな等という目標もあります。
本ブログの(上記Riverpod.dev/docsの)翻訳および解説の目次頁(構成上のトップページ)は次のURLになります。
本頁は、上記のうちの、「Getting started」の章の翻訳および解説頁になります。
Getting started
Try Riverpod online
Riverpodの雰囲気を味わうには、以下を試してみよう。
import 'package:flutter/material.dart';
//状態管理と再利用可能なウィジェットのロジックを簡略化する
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'todo.dart';
///キーの定義
//これらはウィジェットを一意に識別するためのキーです。テストや特定のウィジェット操作に役立ちます。
final addTodoKey = UniqueKey();
final activeFilterKey = UniqueKey();
final completedFilterKey = UniqueKey();
final allFilterKey = UniqueKey();
/// Creates a [TodoList] and initialise it with pre-defined values.
///
/// We are using [StateNotifierProvider] here as a `List<Todo>` is a complex
/// object, with advanced business logic like how to edit a todo.
//プロバイダーの定義
final todoListProvider = NotifierProvider<TodoList, List<Todo>>(TodoList.new);
/// The different ways to filter the list of todos
//Enumの定義
enum TodoListFilter {
all,
active,
completed,
}
/// The currently active filter.
///
/// We use [StateProvider] here as there is no fancy logic behind manipulating
/// the value since it's just enum.
//<TODO>リストのフィルター条件を定義する列挙型です。
//全てのTODO、活動中のTODO、完了したTODOの三つの状態があります。
final todoListFilter = StateProvider((_) => TodoListFilter.all);
/// The number of uncompleted todos
///
/// By using [Provider], this value is cached, making it performant.\
/// Even multiple widgets try to read the number of uncompleted todos,
/// the value will be computed only once (until the todo-list changes).
///
/// This will also optimise unneeded rebuilds if the todo-list changes, but the
/// number of uncompleted todos doesn't (such as when editing a todo).
//現在選択されているフィルター条件を管理するStateProviderです。
final uncompletedTodosCount = Provider<int>((ref) {
return ref.watch(todoListProvider).where((todo) => !todo.completed).length;
});
/// The list of todos after applying of [todoListFilter].
///
/// This too uses [Provider], to avoid recomputing the filtered list unless either
/// the filter of or the todo-list updates.
//未完了のTODO数を計算するプロバイダーです。
//Providerを使用して値がキャッシュされ、パフォーマンスが向上します。
final filteredTodos = Provider<List<Todo>>((ref) {
final filter = ref.watch(todoListFilter);
final todos = ref.watch(todoListProvider);
switch (filter) {
case TodoListFilter.completed:
return todos.where((todo) => todo.completed).toList();
case TodoListFilter.active:
return todos.where((todo) => !todo.completed).toList();
case TodoListFilter.all:
return todos;
}
});
void main() {
runApp(const ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: Home(),
);
}
}
class Home extends HookConsumerWidget {
const Home({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final todos = ref.watch(filteredTodos);
final newTodoController = useTextEditingController();
return GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
child: Scaffold(
body: ListView(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 40),
children: [
const Title(),
TextField(
key: addTodoKey,
controller: newTodoController,
decoration: const InputDecoration(
labelText: 'What needs to be done?',
),
onSubmitted: (value) {
ref.read(todoListProvider.notifier).add(value);
newTodoController.clear();
},
),
const SizedBox(height: 42),
const Toolbar(),
if (todos.isNotEmpty) const Divider(height: 0),
for (var i = 0; i < todos.length; i++) ...[
if (i > 0) const Divider(height: 0),
Dismissible(
key: ValueKey(todos[i].id),
onDismissed: (_) {
ref.read(todoListProvider.notifier).remove(todos[i]);
},
child: ProviderScope(
overrides: [
_currentTodo.overrideWithValue(todos[i]),
],
child: const TodoItem(),
),
),
],
],
),
),
);
}
}
class Toolbar extends HookConsumerWidget {
const Toolbar({
super.key,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final filter = ref.watch(todoListFilter);
Color? textColorFor(TodoListFilter value) {
return filter == value ? Colors.blue : Colors.black;
}
return Material(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
'${ref.watch(uncompletedTodosCount)} items left',
overflow: TextOverflow.ellipsis,
),
),
Tooltip(
key: allFilterKey,
message: 'All todos',
child: TextButton(
onPressed: () =>
ref.read(todoListFilter.notifier).state = TodoListFilter.all,
style: ButtonStyle(
visualDensity: VisualDensity.compact,
foregroundColor:
WidgetStateProperty.all(textColorFor(TodoListFilter.all)),
),
child: const Text('All'),
),
),
Tooltip(
key: activeFilterKey,
message: 'Only uncompleted todos',
child: TextButton(
onPressed: () => ref.read(todoListFilter.notifier).state =
TodoListFilter.active,
style: ButtonStyle(
visualDensity: VisualDensity.compact,
foregroundColor: WidgetStateProperty.all(
textColorFor(TodoListFilter.active),
),
),
child: const Text('Active'),
),
),
Tooltip(
key: completedFilterKey,
message: 'Only completed todos',
child: TextButton(
onPressed: () => ref.read(todoListFilter.notifier).state =
TodoListFilter.completed,
style: ButtonStyle(
visualDensity: VisualDensity.compact,
foregroundColor: WidgetStateProperty.all(
textColorFor(TodoListFilter.completed),
),
),
child: const Text('Completed'),
),
),
],
),
);
}
}
class Title extends StatelessWidget {
const Title({super.key});
@override
Widget build(BuildContext context) {
return const Text(
'todos',
textAlign: TextAlign.center,
style: TextStyle(
color: Color.fromARGB(38, 47, 47, 247),
fontSize: 100,
fontWeight: FontWeight.w100,
fontFamily: 'Helvetica Neue',
),
);
}
}
/// A provider which exposes the [Todo] displayed by a [TodoItem].
///
/// By retrieving the [Todo] through a provider instead of through its
/// constructor, this allows [TodoItem] to be instantiated using the `const` keyword.
///
/// This ensures that when we add/remove/edit todos, only what the
/// impacted widgets rebuilds, instead of the entire list of items.
final _currentTodo = Provider<Todo>((ref) => throw UnimplementedError());
class TodoItem extends HookConsumerWidget {
const TodoItem({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final todo = ref.watch(_currentTodo);
final itemFocusNode = useFocusNode();
final itemIsFocused = useIsFocused(itemFocusNode);
final textEditingController = useTextEditingController();
final textFieldFocusNode = useFocusNode();
return Material(
color: Colors.white,
elevation: 6,
child: Focus(
focusNode: itemFocusNode,
onFocusChange: (focused) {
if (focused) {
textEditingController.text = todo.description;
} else {
// Commit changes only when the textfield is unfocused, for performance
ref
.read(todoListProvider.notifier)
.edit(id: todo.id, description: textEditingController.text);
}
},
child: ListTile(
onTap: () {
itemFocusNode.requestFocus();
textFieldFocusNode.requestFocus();
},
leading: Checkbox(
value: todo.completed,
onChanged: (value) =>
ref.read(todoListProvider.notifier).toggle(todo.id),
),
title: itemIsFocused
? TextField(
autofocus: true,
focusNode: textFieldFocusNode,
controller: textEditingController,
)
: Text(todo.description),
),
),
);
}
}
bool useIsFocused(FocusNode node) {
final isFocused = useState(node.hasFocus);
useEffect(
() {
void listener() {
isFocused.value = node.hasFocus;
}
node.addListener(listener);
return () => node.removeListener(listener);
},
[node],
);
return isFocused.value;
}
import 'package:flutter/foundation.dart' show immutable;
import 'package:riverpod/riverpod.dart';
import 'package:uuid/uuid.dart';
//一意のTODOアイテム用IDを割り当てるのに使用します
const _uuid = Uuid();
/// A read-only description of a todo-item
//不変クラス
@immutable
class Todo {
const Todo({
required this.description,
required this.id,
this.completed = false,
});
final String id;
final String description;
final bool completed;
@override
String toString() {
return 'Todo(description: $description, completed: $completed)';
}
}
/// An object that controls a list of [Todo].
class TodoList extends Notifier<List<Todo>> {
@override
List<Todo> build() => [
const Todo(id: 'todo-0', description: 'Buy cookies'),
const Todo(id: 'todo-1', description: 'Star Riverpod'),
const Todo(id: 'todo-2', description: 'Have a walk'),
];
void add(String description) {
state = [
...state,
Todo(
id: _uuid.v4(),
description: description,
),
];
}
void toggle(String id) {
state = [
for (final todo in state)
if (todo.id == id)
Todo(
id: todo.id,
completed: !todo.completed,
description: todo.description,
)
else
todo,
];
}
void edit({required String id, required String description}) {
state = [
for (final todo in state)
if (todo.id == id)
Todo(
id: todo.id,
completed: todo.completed,
description: description,
)
else
todo,
];
}
void remove(Todo target) {
state = state.where((todo) => todo.id != target.id).toList();
}
}
パッケージのインストール
インストールしたいパッケージがわかったら、次のように1行で依存関係をアプリに追加する:
flutter pub add flutter_riverpod
flutter pub add riverpod_annotation
flutter pub add dev:riverpod_generator
flutter pub add dev:build_runner
flutter pub add dev:custom_lint
flutter pub add dev:riverpod_lint
または、pubspec.yaml 内から手動で依存関係をアプリに追加することもできます:
name: my_app_name
environment:
sdk: ">=3.0.0 <4.0.0"
flutter: ">=3.0.0"
dependencies:
flutter:
sdk: flutter
flutter_riverpod: ^2.5.1
riverpod_annotation: ^2.3.5
dev_dependencies:
build_runner:
custom_lint:
riverpod_generator: ^2.4.2
riverpod_lint: ^2.3.12
次に、flutter pub getでパッケージをインストールします。 これで、flutter pub run build_runner watchでコードジェネレータを実行できます。 以上です。 これでアプリにRiverpodが追加された!
riverpod_lint / costom_lint 有効化
Riverpodにはオプションでriverpod_lintパッケージが付属しており、より良いコードを書くためのlintルールを提供し、カスタムリファクタリングオプションを提供します。
前のステップに従えば、パッケージはすでにインストールされているはずだが、有効にするには別のステップが必要だ。
riverpod_lintを有効にするには、pubspec.yamlの隣にanalyze_options.yamlを追加し、以下を含める必要があります:
include: package:flutter_lints/flutter.yaml
analyzer:
plugins:
- custom_lint
linter:
コードベースでRiverpodを使用する際にミスがあった場合、IDEで警告が表示されるようになりました。
警告とリファクタリングの全リストを見るには、riverpod_lintページにアクセスしてください。
補足) riverpod_generator で自動生成をサポートしている4種類のProvider
- 標準Provider
- FutureProvider
- NotifierProvider
- AsyncNotifierProvider
1.標準Provider 例:@riverpod String greeting(GreetingRef ref) { return 'This is format of plain Provider for @riverpod!'; }
引数: 関数名の1文字目(例:g)を大文字(例:G)にして”Ref”を付ける
2.FutureProvider 例:@riverpod Future<String> greeting(GreetingRef ref) async { await Future.delayed(const Duration(seconds: 1)); // 擬似的な遅延 return 'This is format of future Provider for @riverpod!'; }
引数: 関数名の1文字目(例:g)を大文字(例:G)にして”Ref”を付ける
3.NotifierProvider 例:@freezed class Day with _$Day { factory Day({ required String date, }) = _Day; } @riverpod class Greeting extends _$Greeting { @override List<Day> build() { return []; } }
継承: クラス名の頭に、” _$ “を付けたクラスを、extends する
書式: build() を、@override する4.AsyncNotifierProvider 例:@freezed class Day with _$Day { factory Day({ required String date, }) = _Day; } @riverpod class Greeting extends _$Greeting { @override Future<List<Day>> build() async{ return []; } }
継承: クラス名の頭に、” _$ “を付けたクラスを、extends する
書式: build() を、@override し、かつ、Future と async をつける
使用例:Hello world
Riverpodをインストールしたので、さっそく使ってみよう。
次のスニペットは、新しい依存関係を使って “Hello world “を作る方法を示している:
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'main.g.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.
@riverpod
String helloWorld(HelloWorldRef ref) {
return '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.
const ProviderScope(
child: MyApp(),
),
);
}
// Extend ConsumerWidget instead of StatelessWidget, which is exposed by Riverpod
class MyApp extends ConsumerWidget {
const MyApp({super.key});
@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),
),
),
);
}
}
Going further: Installing code snippets
さらに進む: コード スニペットのインストール
FlutterとAndroid StudioまたはIntelliJを使用している場合は、Flutter Riverpod Snippetsの使用を検討してください。
( URL: https://marketplace.visualstudio.com/items?itemName=robert-brunhage.flutter-riverpod-snippets )
Choose your next step
以降のステップは3つあります。
- Provider基本概念:本Verから学習する方向け ⇒ “Learn som basic concepts: ・Providers“
- 本編:旧Ver習得済の方向け ⇒ > “”>”Next: Make your first provider/network request >> “
- CookBook:上記完了者向け ⇒ “Follow a cookbook: Testing“
ここでは上記の順番に進めます。
(次の記事はこちら)