コンテンツにスキップ

状態管理

Flutter 状態管理  Riverpod(StateNotifier) x Freezed #Flutter - Qiita

Provider

【Flutter】個人的 Riverpod の理解まとめ

  • Riverpod には Notifier と Provider の概念がある。
  • 値を取得したい人は Provider へアクセスする。値を変更したい人は Notifier へアクセスする。
  • Widget から値を参照するには以下に記載する ConsumerWidget もしくは ConsumerStatefulWidget を継承しておき、WidgetRef refを使って参照する。
  • ref.watch(provider)で取得した値は値が更新されたとき、自動的に画面に反映される。
  • ref.read(provider)はコード的に同期的な取得であり、その後に参照先の値が更新されても画面の値はそのままである。
  • ref.listen(provider)はコールバック関数的なものを登録できるらしい。

Provider の種類まとめ

種類 更新(*1) 読み込み 書き込み メソッド実装 備考
Provider ✅️ ✅️ - -
FutureProvider ✅️ ✅️ - - Provider と似ているが、初期値の用意を非同期にできる点で異なる。
StateProvider ✅️ ✅️ ✅️ -
NotifierProvider ✅️ ✅️ ✅️ ✅️
AsyncNotifierProvider ✅️ ✅️ ✅️ ✅️ NotifierProvider と似ているが、初期値の用意を非同期にできる点で異なる。
StreamProvider WebSocket などのストリームを扱いたいときに使うらしい。
  1. Riverpod で一度取得した値はキャッシュされる。そのキャッシュした値を破棄して再度取得することを更新と表記している。

Provider

ユースケース

  • アプリにグローバル定数を提供したい。(Riverpod が更新方法を提供していないだけで、保持している値が定数になるではわけではない点に注意)

サンプル

1
2
3
4
5
import 'dart:math';

import "package:flutter_riverpod/flutter_riverpod.dart";

final sampleProvider = Provider<int>((_) => Random().nextInt(100));

FutureProvider

Future を扱う Provider。初期値の設定を非同期で行わないといけない場合はこれ。

ユースケース

  • HTTP 通信の結果をアプリの定数として扱いたい。

サンプル

1
2
3
4
5
6
7
8
import 'dart:math';

import 'package:flutter_riverpod/flutter_riverpod.dart';

final sampleFutureProvider = FutureProvider<int>((ref) async {
  await Future.delayed(const Duration(seconds: 3));
  return Future<int>.value(Random().nextInt(100));
});

StateProvider

Provider を更新可能にしたやつ。グローバル変数的。

ユースケース

  • アプリにグローバル変数を提供したい。

サンプル

1
2
3
4
5
6
7
import 'dart:math';

import 'package:flutter_riverpod/flutter_riverpod.dart';

// StateProviderは以下のコードで更新することができる
// ref.read(sampleVariableValueProvider.notifier).update((state) => newValue);
final sampleStateProvider = StateProvider<int>((ref) => Random().nextInt(100));

NotifierProvider

StateProvider に状態を更新するメソッドを加えられるやつ。

ユースケース

  • 状態を保持したい。状態の変更は複雑な処理を挟むのでメソッドで隠蔽・共通化したい。

サンプル

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import 'dart:math';

import 'package:riverpod_annotation/riverpod_annotation.dart';

class SampleNotifier extends Notifier<int> {
  @override
  int build() {
    return Random().nextInt(100);
  }

  increment() {
    state = state + 1;
  }

  decrement() {
    state = state - 1;
  }
}

final sampleNotifierProvider = NotifierProvider<SampleNotifier, int>(
  () => SampleNotifier(),
);

似た名前のStateNotifierProviderは非推奨。

AsyncNotifierProvider

AsyncNotifier の使い方| Riverpod について学ぶ

初期化を非同期でできる版のNotifierProvider

ユースケース

  • HTTP 通信の結果をアプリの状態として保持したい。状態の変更は複雑な処理を挟むのでメソッドで隠蔽・共通化したい。

サンプル

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import 'dart:math';

import 'package:riverpod_annotation/riverpod_annotation.dart';

class SampleAsyncNotifier extends AsyncNotifier<int> {
  @override
  Future<int> build() async {
    await Future.delayed(const Duration(seconds: 3));
    return Future<int>.value(Random().nextInt(100));
  }

  increment() {
    update((data) => data + 1);
  }

  decrement() {
    update((data) => data - 1);
  }
}

final sampleAsyncNotifierProvider =
    AsyncNotifierProvider<SampleAsyncNotifier, int>(
  () => SampleAsyncNotifier(),
);

ChangeNotifierProvider

非推奨のようなので調べない。

StreamProvider

Stream を扱う Provider。

AsyncNotifierProvider の refresh 時に loading 状態にしたい

when()の引数にskipLoadingOnRefresh: falseを追加する。

AsyncNotifierProvider の refresh 時に loading 状態にする #Flutter - Qiita

分類不明

AsyncValue

AsyncValue class - riverpod library - Dart API

Notifier、Provider を使う Widget

ConsumerWidget

ConsumerWidget class - flutter_riverpod library - Dart API

WidgetRef refを使えるStatelessWidget

ConsumerStatefulWidget

ConsumerStatefulWidget class - flutter_riverpod library - Dart API

WidgetRef refを使えるStatefulWidget

以下ボイラープレート

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import 'package:flutter/src/widgets/framework.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

class LogInScreen extends ConsumerStatefulWidget {
  const LogInScreen({super.key});

  @override
  ConsumerState<ConsumerStatefulWidget> createState() => _LogInState();
}

class _LogInState extends ConsumerState<LogInScreen> {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    throw UnimplementedError();
  }
}

ローディング画面の表示について

ローディング画面の表示状態はビューに持たせることにする。理由は以下にある。

  • AsyncNotifier の build()時に状態の更新をしようとするとエラーになるから

`doesn't conform to the bound 'Notifier' of the type parameter 'NotifierT'のエラーが出る

provider のジェネリクスの第一引数に指定したクラスはNotifier<T>を継承しているか確認する。

1
2
3
final httpRequestProvider = NotifierProvider<IHttpRequestNotifier, HttpRequest>(
  () => throw UnimplementedError(),
);