JINMUSOFTWARE

Riverpod 3 入門 ~AsyncNotifier編~

AsyncNotifierProviderの簡単な説明です。今回もgenerator未使用です。

AsyncNotifierProviderとは?

AsyncNotifierProviderは、非同期処理が可能で、stateの変更が可能なproviderです。

WebAPI経由のCRUD操作やsqflite操作のサンプルでよく利用されています。

SynchronousFutureStream
UnmodifiableProviderFutureProviderStreamProvider
ModifiableNotifierProviderAsyncNotifierProviderStreamNotifierProvider

環境

  • Flutter 3.38.6
  • flutter_riverpod 3.2.0

Install

flutter_riverpod パッケージのインストール

Bash
flutter pub add flutter_riverpod

lint設定

YAML
plugins:
  riverpod_lint: ^3.1.2

AsyncNotifierProvider

巷では、Riverpod3以降のサンプルも少なく、ChatGPTもClaudeもGitHub Copilotもデータが少ないのか適当言ってきますが頑張りましょう。

とっても簡単なproviderを作ります。

カウンター機能を持ったproviderです。

ファイル名:counter_provider.dart

counter_provider.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';

class Counter extends AsyncNotifier<int> {
  @override
  Future<int> build() async {
    return 0;
  }

  Future<void> increment() async {
    state = const AsyncValue.loading(); // お好みで
    await Future.delayed(const Duration(milliseconds: 800));
    state = AsyncValue.data((state.value ?? 0) + 1);
  }
}

// AsyncNotifierProviderの定義
final counterProvider = AsyncNotifierProvider<Counter, int>(() {
  return Counter();
});

ちょっとわざとらしくwaitがありますね。

さて、これでもいいのですが、本当はここにAPI取得とかDB操作とか記述されます。例外対応もしておきたいで、try/catch構文を追加してみましょう。

increment()
state = const AsyncValue.loading();
try {
  await Future.delayed(const Duration(milliseconds: 800));
  state = AsyncValue.data((state.value ?? 0) + 1);
} catch (e, st) {
  state = AsyncValue.error(e, st);
}

AsyncValueにはguardが使えますね。

次のように変更してみましょう。

increment()
state = const AsyncValue.loading();
state = await AsyncValue.guard(() async {
  await Future.delayed(const Duration(milliseconds: 800));
  return (state.value ?? 0) + 1;
});

「state = const AsyncValue.loading();」は外してもいいです。長い処理のときはあったほうが親切かもね。

main側

ConsumerWidgetで受信します。

loading中はボタンを押せないようにしています。

main code
Dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'counter_provider.dart';

void main() {
  runApp(const ProviderScope(child: MyApp()));
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'AsyncNotifierProvider',
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends ConsumerWidget {
  const MyHomePage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // watch!!
    final counterAsync = ref.watch(counterProvider);

    return Scaffold(
      appBar: AppBar(title: const Text('非同期カウンター')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text('ボタンを押すと数秒後にカウントアップします'),
            const SizedBox(height: 20),
            counterAsync.when(
              data: (count) => Text(
                '$count',
                style: Theme.of(context).textTheme.headlineLarge,
              ),
              loading: () => const CircularProgressIndicator(),
              error: (error, stack) => Text('エラー: $error'),
            ),
            const SizedBox(height: 20),
            ElevatedButton.icon(
              onPressed: counterAsync.isLoading
                  ? null
                  : () => ref.invalidate(counterProvider),
              icon: const Icon(Icons.refresh),
              label: const Text('リフレッシュ'),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        // これは、多重に押せてしまう。
        // onPressed: () => ref.read(counterProvider.notifier).increment(),
        //
        onPressed: counterAsync.isLoading
            ? null
            : () => ref.read(counterProvider.notifier).increment(),
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

参考サイト

参考ページ