はじめに
Flutterの状態管理パッケージにRiverpodがあります。色々調べていたのですが、イマイチわかりづらかったので実際に簡単なTodoアプリっぽいものを作りました。その過程をメモしておきます。
開発物
- Listへ追加と削除を行うだけのもの
- ロジックの理解が主目的なので機能とUIは最小限
開発環境
- Flutter 2.2.0
- Dart 2.13.0
画面構成
- HomeScreen
- Listを表示する画面
- PostScreen
- ListにItemを追加する画面
コード
RiverpodとHooksの導入
導入するときはRiverpodとHooksのバージョン対応に注意してください。また、uniqueなIDを生成するためにuuid
パッケージも導入しています。
dependencies:
flutter:
sdk: flutter
flutter_hooks: ^0.17.0
hooks_riverpod: ^0.14.0+4
uuid: ^3.0.5
Taskモデルの実装
Taskモデルを定義していきます。今回はDone判定の実装はしません。本格的なTodoアプリの作り方は他の方が記事をあげているのでそちらを参照してください。
import 'package:uuid/uuid.dart';
var _uuid = Uuid();
class Task {
Task({
this.title,
String? id,
}) : id = id ?? _uuid.v4();
final String? id;
final String? title;
}
コントローラの実装
TaskのListをコントロールするTaskListをStateNotifier
で実装。
Provider
の宣言もここで行っています。
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:state_notifier/state_notifier.dart';
/* models */
import './task.dart';
//Providerの宣言
final StateNotifierProvider<TaskList, dynamic> taskListProvider =
StateNotifierProvider((ref) => TaskList([]));
class TaskList extends StateNotifier<List<Task>> {
TaskList(List<Task> initialTask) : super(initialTask ?? []);
//新規タスクの追加
void addTask(String title) {
state = [...state, Task(title: title)];
}
//タスクの削除
void deleteTask(Task target) {
state = state.where((task) => task.id != target.id).toList();
}
}
タスクの追加・削除処理の詳細については省略しますが、単純にListを作り直しているだけですね。
Task追加画面の実装
先ほど作ったTaskListをインポートします。
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
/* models */
import '../models/task_list.dart';
class PostScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
String _title = '';
return Scaffold(
appBar: AppBar(
title: Text("Add"),
),
body: Container(
padding: EdgeInsets.all(64),
width: double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextField(
onChanged: (String value) {
_title = value;
},
),
ElevatedButton(
onPressed: () => {
context.read(taskListProvider.notifier).addTask(_title),
Navigator.pop(context)
},
child: Text("add"),
),
],
),
),
);
}
}
TaskListのメソッドを呼び出すにはcontext.read(taskListProvider.notifier)
でStateNotifier
を取得します。TaskListのaddTask()
を呼び出して、TextFieldから取得した値を引数に渡します。これでTaskListの状態を更新できます。
リスト画面の実装
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
/* models */
import 'package:todo_test/models/task_list.dart';
class HomeScreen extends HookWidget {
@override
Widget build(BuildContext context) {
final taskList = useProvider(taskListProvider);
return Center(
child: ListView.builder(
itemCount: taskList.length,
itemBuilder: (context, index) {
final task = taskList[index];
return ListTile(
title: Text(task.title.toString()),
onTap: () => {
context.read(taskListProvider.notifier).deleteTask(task)
},
trailing: Icon(Icons.auto_delete),
);
},
),
);
}
}
ここでHooksを活用します。useProvider
で状態の変更をキャッチし、変数taskList
に代入しています。また、ListTile
をタップするとdeleteTask()
が呼び出され、Listから削除します。
main
ここを忘れるとRiverpodを利用できないので注意してください。Riverpodを使用するにはProviderScopeで全体をwrapする必要があります。
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
/* screens */
import './screens/template_screen.dart';
void main() {
runApp(
ProviderScope(
child: MyApp(),
)
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: TemplateScreen()
);
}
}
ここで少しハマったのですが、MaterialApp直下でNavigatorを置くとエラーになりました。間にワンクッション空けて記載した方が好ましい様です。今回はTemplateScreenを実装しました。
import 'package:flutter/material.dart';
import 'package:todo_test/screens/home_screen.dart';
/* screens */
import './home_screen.dart';
import'./post_screen.dart';
class TemplateScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("List"),
),
body: HomeScreen(),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () => {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => PostScreen()),
),
},
),
);
}
}
以上です。
感想
状態管理はアプリの根幹なので、理解が曖昧なら一度テストコードを作ってみた方が良さそうです。当方Flutter初心者なので至らぬ点もありますが、ご容赦ください。
Github
今回作ったアプリはGithubに載せておきます。
Flutterのプロジェクト名とGithubのリポジトリ名が異なっているので注意してください。
https://github.com/oyachi/riverpod_test