このチャプターの目次
非同期データを安全に操作するためのユーティリティです。
AsyncValueを使用することで、非同期操作のロード/エラー状態の処理を忘れることがないことが保証されます。
また、AsyncValueを別のオブジェクトにうまく変換するためのユーティリティもいくつか公開されている。例えば、Flutter WidgetはAsyncValueをプログレスインジケータ、エラー画面、またはデータの表示のいずれかに変換するときに使用することができる。
失敗するかもしれないFutureを、安全に読めるものに変換する。
これは、面倒な try/catch をしなくてすむようにするために便利です。代わりに
class MyNotifier extends StateNotifier<AsyncValue<MyData> {
MyNotifier(): super(const AsyncValue.loading()) {
_fetchData();
}
Future<void> _fetchData() async {
state = const AsyncValue.loading();
try {
final response = await dio.get('my_api/data');
final data = MyData.fromJson(response);
state = AsyncValue.data(data);
} catch (err, stack) {
state = AsyncValue.error(err, stack);
}
}
}
使用されるユースケース
公式の翻訳通りに、AsyncValueを使用することで、非同期操作のロード/エラー状態の処理を忘れることがないことが保証されます。
例外処理が発生したら、スナックバーを出したりローディング処理を表示するのに、使われます。
匿名ログインを使用したデモアプリを作成いたしました。こちらは後ほど紹介するAsyncNotifierと機能自体は変わりません。
AsyncValueを使用したStateNotifier
import 'dart:developer';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:state_tutorial/auth/auth_page.dart';
import 'package:state_tutorial/auth/hello_page.dart';
// FirebaseAuthをインスタンス化したプロバイダー.
final authRepositoryProvider =
Provider<FirebaseAuth>((ref) => FirebaseAuth.instance);
//
class AuthController extends StateNotifier<AsyncValue<void>> {
AuthController(this.ref) : super(const AsyncData(null));
final Ref ref;
// 匿名ログインを行うメソッド.
Future<void> signInAnonymously(BuildContext context) async {
final authRepository = ref.read(authRepositoryProvider);
state = const AsyncLoading();
state = await AsyncValue.guard(authRepository.signInAnonymously);
log(state.toString());
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(builder: (context) => const HelloPage()),
(route) => false);
}
// ログアウトを行うメソッド.
Future<void> signOutAnonymously(BuildContext context) async {
final authRepository = ref.read(authRepositoryProvider);
state = const AsyncValue.loading();
state = await AsyncValue.guard(() => authRepository.signOut());
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(builder: (context) => const AuthPage()),
(route) => false);
}
}
// StateNotifierを外部ファイルで呼び出すプロバイダー.
final authControllerProvider =
StateNotifierProvider<AuthController, AsyncValue<void>>((ref) {
return AuthController(ref);
});
ローディングを表示するボタン
コールバック関数利用して、ref.listenの第一引数にプロバイダーを渡して、古い状態と新しい状態によって、処理を行う。例外処理が発生すると、スナックバーを表示して、ログインする場合は、ローディングの処理が発生する。
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:state_tutorial/auth/auth_state.dart';
// auth button that can be used for signing in or signing out
class AuthButton extends ConsumerWidget {
const AuthButton({super.key, required this.text, required this.onPressed});
final String text;
final VoidCallback onPressed;
Widget build(BuildContext context, WidgetRef ref) {
ref.listen<AsyncValue<void>>(
authControllerProvider,
(_, state) {
if (state.hasError) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(state.asError.toString())),
);
}
},
);
final state = ref.watch(authControllerProvider);
return SizedBox(
width: 200,
height: 60,
child: ElevatedButton(
onPressed: state.isLoading ? null : onPressed,
child: state.isLoading
? const CircularProgressIndicator()
: Text(text,
style: Theme.of(context)
.textTheme
.headline6!
.copyWith(color: Colors.white)),
),
);
}
}
匿名ログインを行うページ
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:state_tutorial/auth/auth_button.dart';
import 'package:state_tutorial/auth/auth_state.dart';
class AuthPage extends ConsumerWidget {
const AuthPage({Key? key}) : super(key: key);
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: AppBar(
title: const Text('AsyncNotifier'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
AuthButton(
text: '登録せずに利用',
onPressed: () => ref
.read(authControllerProvider.notifier)
.signInAnonymously(context)),
],
),
),
);
}
}
ログアウトするページ
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:state_tutorial/auth/auth_state.dart';
class HelloPage extends ConsumerWidget {
const HelloPage({Key? key}) : super(key: key);
Widget build(BuildContext context, WidgetRef ref) {
final authController = ref.read(authControllerProvider.notifier);
return Scaffold(
appBar: AppBar(
actions: [
IconButton(
onPressed: () async {
authController.signOutAnonymously(context);
},
icon: const Icon(Icons.logout))
],
title: const Text('Auth'),
),
body: Center(child: Text('Hello World')),
);
}
}
アプリを実行するコード
main.dart
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:state_tutorial/auth/auth_page.dart';
import 'firebase_options.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const AuthPage(),
);
}
}
実行結果