NotifierProvider は、Notifier をリッスンして公開するために使用されるプロバイダです。AsyncNotifier は、非同期に初期化することができる Notifier です。AsyncNotifierProvider は、AsyncNotifier をリッスンして公開するために使用されるプロバイダです。
(Async)NotifierProviderと(Async)Notifierは、ユーザーの操作に反応して変化する状態を管理するためのRiverpodの推奨ソリューションである。
これは、通常、次のような用途に使用されます。
カスタムイベントに反応した後、時間の経過とともに変化する可能性のある状態を公開する。
状態を変更するためのロジック(別名「ビジネスロジック」)を一箇所に集中させ、長期的な保守性を向上させる。
使用例として、Todo-Listを実装するためにNotifierProviderを使用することができます。そうすることで、addTodoのようなメソッドを公開し、ユーザーインタラクションでUIがTodoのリストを変更できるようにします。
出たばかりで、あまり情報がなかったので、CODE ANDDREAを参考にして、匿名ログインの機能を実装するプログラクムを作成しました。
登録せずに利用のボタンを押すと、ローディング処理がボタンの位置に表示され、Hello Worldしてくれるページへ移動します。このときに、匿名ログインをしたユーザーの情報がFirebaseAuthenticationに登録されます。
コールバック関数の知識も必要な場面があるので、解説します。
https://codewithandrea.com/articles/flutter-riverpod-async-notifier/#how-does-asyncnotifier-work?
AsyncNotifierはどのように機能するのですか?
Riverpodのドキュメントでは、AsyncNotifierを非同期に初期化されるNotifierの実装として定義しています。
そして、それを使ってauthAsyncNotifierControllerクラスを変換する方法を紹介する。
callbackとは?
コールバック関数とは、引数として他の関数に渡され、外側の関数の中で呼び出されて、何らかのルーチンやアクションを完了させる関数のことです。
簡単な例を以下に示します。
function greeting(name) {
alert(`Hello, ${name}`);
}
function processUserInput(callback) {
const name = prompt("Please enter your name.");
callback(name);
}
processUserInput(greeting);
Dartだとこんな感じですね。
void main() {
greet('Hi');
callBack(greet);
}
void greet(String s) {
print(s);
}
void callBack(Function function) {
print(function);
}
匿名認証について
使うのは簡単でFirebaseのコンソールから設定をして、コードを書くだけでできます。
AsyncNotifierを定義する
状態を持っている変数とメソッドを扱うことができるAsyncNotifierを定義します。今回だと匿名でログインをすると、ボタンにローディングが表示される機能を実装しました。
// 1. add the necessary imports
import 'dart:async';
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/about_asyn_notifier/auth_page.dart';
import 'package:state_tutorial/about_asyn_notifier/hello_page.dart';
// authAsyncNotifierControllerを外部ファイルで呼び出すプロバイダー.
final authAsyncNotifierController = AsyncNotifierProvider(AuthController.new);
// FirebaseAuthをインスタンス化したプロバイダー.
final authRepositoryProvider =
Provider<FirebaseAuth>((ref) => FirebaseAuth.instance);
class AuthController extends AsyncNotifier<void> {
FutureOr<void> build() {
// 値を返す(返り値が void ならば何もしない)。
}
// 匿名認証でログインするメソッド
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 AsyncNotifierAuth()),
(route) => false);
}
}
コンポーネントに引数を渡す
プロバイダーをボタンコンポーネントのref.listenの第一引数に渡します。これは、ただのコールバック関数だそうで、やっていることは、AsyncNotifierを引数として渡してボタンが押されると、ローディング処理が実行される仕組みになっています。
もし、エラーが発生したらスナックバーを表示します。
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;// VoidCallback型を指定すると、ref.readを書くことができる.
Widget build(BuildContext context, WidgetRef ref) {
// ref.listenを使ってプロバイダーをコールバック関数の引数に渡す.
ref.listen<AsyncValue<void>>(
authAsyncNotifierController,
(_, state) {
if (state.hasError) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(state.asError.toString())),
);
}
},
);
final state = ref.watch(authAsyncNotifierController);
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/about_asyn_notifier/auth_controller.dart';
import 'package:state_tutorial/auth/auth_button.dart';
class AsyncNotifierAuth extends ConsumerWidget {
const AsyncNotifierAuth({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(authAsyncNotifierController.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(authAsyncNotifierController.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')),
);
}
}
アプリを実行するコード
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:state_tutorial/about_asyn_notifier/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 AsyncNotifierAuth(),
);
}
}
実行結果