1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[Flutter] RiverpodとHooksで簡易的な状態管理をしたい

Last updated at Posted at 2021-10-20

はじめに

Flutterの状態管理パッケージにRiverpodがあります。色々調べていたのですが、イマイチわかりづらかったので実際に簡単なTodoアプリっぽいものを作りました。その過程をメモしておきます。

開発物

  • Listへ追加と削除を行うだけのもの
  • ロジックの理解が主目的なので機能とUIは最小限
flutter_todo.gif

開発環境

  • Flutter 2.2.0
  • Dart 2.13.0

画面構成

  • HomeScreen
    • Listを表示する画面
  • PostScreen
    • ListにItemを追加する画面

コード

RiverpodとHooksの導入

導入するときはRiverpodとHooksのバージョン対応に注意してください。また、uniqueなIDを生成するためにuuidパッケージも導入しています。

pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  flutter_hooks: ^0.17.0
  hooks_riverpod: ^0.14.0+4
  uuid: ^3.0.5

Taskモデルの実装

Taskモデルを定義していきます。今回はDone判定の実装はしません。本格的なTodoアプリの作り方は他の方が記事をあげているのでそちらを参照してください。

task.dart
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の宣言もここで行っています。

task_list.dart
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をインポートします。

post_screen.dart
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の状態を更新できます。

リスト画面の実装

home_screen.dart
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する必要があります。

main.dart
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を実装しました。

template_screen.dart
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

参考

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?