0
0

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 1 year has passed since last update.

Riverpod v2を使用してページとページの状態を1対1で紐つけて簡単に管理する【Flutter】

Posted at

はじめに

今回は、Flutterで簡単に状態管理をする手法について、備忘録的に書いておく記事です。
内容が間違っている場合、もっといいやり方がある場合はコメントにてお願いします。

今回の環境

  • Flutter 3.10.1
  • flutter_riverpod 2.4.10
  • riverpod_generator 2.3.11
  • riverpod_annotation 2.3.4
  • freezed 2.5.2
  • freezed_annotation 2.4.1
  • build_runner 2.4.9

今回の記事でやること

以下の図のような状態管理をします。

無題の図形描画 (1).png

viewとstateが1対1(または1対多)で紐ついています。

controllerはviewModelを言い換えても良いです。modelから作られたインスタンスの状態を返したり、インスタンスの状態を変更するメソッドもcontrollerが持ちます。

実際のコード

viewのコード

test.dart
import 'package:advanced_search_2/ui/state.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

class TestPage extends StatelessWidget {
  const TestPage({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: "Riverpod"),
    );
  }
}

class MyHomePage extends ConsumerWidget { // riverpodを使うためConsumerWidget
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    //  ↓ contollerの状態やメソッドを呼び出すためのコード ↓
    final counter =
        ref.watch(testPageStateProvider.select((value) => value.counter));

    final incrementCounter =
        ref.read(testPageStateProvider.notifier).incrementCounter;

    final decrementCounter =
        ref.read(testPageStateProvider.notifier).decrementCounter;

    final doubleCounter =
        ref.read(testPageStateProvider.notifier).doubleCounter;

    final clearCounter = ref.read(testPageStateProvider.notifier).clearCounter;

    //  ↑ contollerの状態やメソッドを呼び出すためのコード ↑
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$counter',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
            const SizedBox(height: 20),
            ElevatedButton(
                onPressed: () {
                  incrementCounter();
                },
                child: const Text("increment")),
            ElevatedButton(
                onPressed: () => decrementCounter(),
                child: const Text("decrement")),
            ElevatedButton(
                onPressed: () => doubleCounter(), child: const Text("double")),
            ElevatedButton(
                onPressed: () => clearCounter(), child: const Text("clear")),
          ],
        ),
      ),
    );
  }
}

controller・modelのコード

state.dart
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'state.freezed.dart';
part 'state.g.dart';

// 管理する状態とその初期値を書きます
@freezed
class TestState with _$TestState {
  const factory TestState({
    required int counter,
    required String title,
    required String message,
    String? description,
  }) = _TestState;
}

@riverpod
final class TestPageState extends _$TestPageState {
  @override
  TestState build() {
    init();
    return const TestState(
      counter: 0,
      title: "RIVERPOD",
      message: 'You have pushed the button this many times:',
    );
  }

  // 状態を更新するメソッドを書きます
  void incrementCounter() {
    state = state.copyWith(counter: state.counter + 1);
  }

  void decrementCounter() {
    state = state.copyWith(counter: state.counter - 1);
  }

  void doubleCounter() {
    state = state.copyWith(counter: state.counter * 2);
  }

  void clearCounter() {
    state = state.copyWith(counter: 0);
  }
}

dart run build_runner buildの実行を忘れずに!

以上のコードで基本的な状態管理は可能です。
次はオプション的な記述を紹介します。

.autoDisposeを無効化

riverpod v2 以降では、autoDisposeがデフォルトで付与されています。
これを無効化(コントローラーの状態を破棄させない)するためには、明示的に書いてあげる必要があります。

- @riverpod
+ @Riverpod(keepAlive: true)
final class TestPageState extends _$TestPageState {

こうすることで、ページ遷移などで状態が破棄されることはなくなります。

Provider生成時に処理を実行する

今回の例で言うと、counterの値をmodelの初期値と違う値にしたい場合。
以下のような書き方をすることで、Provider生成時に処理を実行することができます。

final class TestPageState extends _$TestPageState {
  @override
  TestState build() {
    state = const TestState(
      counter: 0,
      title: "RIVERPOD",
      message: 'You have pushed the button this many times:',
    );
    init();
    return state;
  }

  void init() {
    state = state.copyWith(counter: 5);
  }
// ~~~~~~ 省略 ~~~~~~

こうすることで、stateをinit()で書き換えた、counter: 5を初期値とすることができます。
非同期の処理を挟みたい場合はAsyncNotifierProviderを使用する必要があります(多分)。

おわりに

お読みいただきありがとうございました!
Riverpodはv1しか触ってこなかったので、違和感がすごいですね...
riverpodを使用した状態管理はググるのがむずかしいです。(v1,v2やannotation、fooksの使用不使用などなど...)
riverpodのGitHubでissueやDiscussionsを掘るのが正解な気がします。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?