🪃
AsyncNotifierでref.listenを使う!
Overview
以前、AsyncNotifierとref.listenを使用して、エラー処理を作っていたのですがうまくいきませんでした。
今回試しに、コードを書いてたらうまくいったので記事にしようと思いました。
エラー状態の AsyncValue を作成します。
StackTrace を持っていません。StackTrace.current.AsyncError を渡すことで、AsyncError を構築できます:
どのような時に使うかというと、AsyncNotifierで、非同期処理のエラーハンドリングをするときです。
summary
今回だと、Firestoreにデータをaddするときに、入力フォームの値がnull
だったらref.listenを使って、スナックバーを出すエラーハンドリングをしています。
Firestoreにデータを保存するときに、stateの管理をして、nullだったら、AsyncErrorを使うロジックです。
import 'dart:async';
import 'package:architecture_app/src/features/app/data/post_provider.dart';
import 'package:architecture_app/src/features/app/domain/post/post.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
final postNotifierProvider =
AsyncNotifierProvider<PostNotifier, void>(PostNotifier.new);
class PostNotifier extends AsyncNotifier<void> {
FutureOr<void> build() {
// 戻り値がなければ書かない。
}
Future<void> sendPost(Post post) async {
final postRef = ref.read(fireStoreProvider);
// 入力された値がnullだったら、ref.listenでエラーを表示する
if (post.body.isEmpty) {
state = const AsyncError<void>('投稿失敗: 本文が空です', StackTrace.empty);
}
// 入力された値がnullじゃなかったら、投稿する
else {
try {
state = const AsyncLoading();
await postRef.collection('post').add(post.toJson());
state = const AsyncValue<void>.data(null); // Successfully added
// ignore: avoid_catches_without_on_clauses
} catch (e, stackTrace) {
// ignore: noop_primitive_operations
state = AsyncError<void>('投稿失敗: ${e.toString()}', stackTrace);
}
}
}
}
ポイントは、AsyncErrorを使う時には、第1引数と第2引数が必要です。
// 入力された値がnullだったら、ref.listenでエラーを表示する
if (post.body.isEmpty) {
state = const AsyncError<void>('投稿失敗: 本文が空です', StackTrace.empty);
}
View側はこんな感じになってます。null のデータが入ってきたら、スナックバーが出ます。
import 'package:architecture_app/src/features/app/application/post_notifier.dart';
import 'package:architecture_app/src/features/app/domain/post/post.dart';
import 'package:architecture_app/src/features/auth/application/auth_notifier.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
/// [ログイン後のページ]ここで、投稿と表示をする
class PostPage extends ConsumerWidget {
const PostPage({super.key});
Widget build(BuildContext context, WidgetRef ref) {
final bodyController = TextEditingController();
ref.listen<AsyncValue<void>>(
postNotifierProvider,
(prev, next) {
if (next is AsyncError) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(next.error.toString())),
);
}
},
);
return Scaffold(
appBar: AppBar(
actions: [
IconButton(
icon: const Icon(Icons.lock),
onPressed: () async {
await ref.read(authNotifierProvider.notifier).signOut();
},
),
],
title: const Text('Post'),
),
body: Center(
child: Column(
children: [
SizedBox(
width: 300,
height: 50,
child: TextFormField(
controller: bodyController,
decoration: const InputDecoration(
hintText: '本文',
border: OutlineInputBorder(),
),
),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () async {
final post = Post(
body: bodyController.text,
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
);
await ref.read(postNotifierProvider.notifier).sendPost(post);
},
child: const Text('投稿')),
],
),
),
);
}
}
入力した値がnullだったらこのようにスナックバーが出てきます!
thoughts
参考になりそうな情報がないので、手探りで今回エラーハンドリングする方法を作ってみました。こちらに作っている途中ですがソースコードがあるので、参考にされてみてください。
Githubのソースコード
AsyncErrorとはなんだったのかというと、機能が集約されたクラスのようです。
/// {@macro asyncvalue.error_ctor}
class AsyncError<T> extends AsyncValue<T> {
/// {@macro asyncvalue.error_ctor}
const AsyncError(Object error, StackTrace stackTrace)
: this._(
error,
stackTrace: stackTrace,
isLoading: false,
hasValue: false,
value: null,
);
const AsyncError._(
this.error, {
required this.stackTrace,
required T? value,
required this.hasValue,
required this.isLoading,
}) : _value = value,
super._();
final bool isLoading;
final bool hasValue;
final T? _value;
T? get value {
if (!hasValue) {
throwErrorWithCombinedStackTrace(error, stackTrace);
}
return _value;
}
final Object error;
final StackTrace stackTrace;
R map<R>({
required R Function(AsyncData<T> data) data,
required R Function(AsyncError<T> error) error,
required R Function(AsyncLoading<T> loading) loading,
}) {
return error(this);
}
AsyncError<T> copyWithPrevious(
AsyncValue<T> previous, {
bool isRefresh = true,
}) {
return AsyncError._(
error,
stackTrace: stackTrace,
isLoading: isLoading,
value: previous.valueOrNull,
hasValue: previous.hasValue,
);
}
}
英語のコメントを翻訳するとこんなことが書かれていました。
私の解釈ですけど、AsyncValue型のNotifierに、whenメソッドを追加してエラー処理ができるようにしたものなのかなと思いました。
間違ってたらすいません🙇♂️
Discussion