💽
StreamProviderでログインを維持する
最近流行りの技術構成で作った!
RiverpodのStreamProviderでログイン状態を維持する方法があったので、興味があって今回demoアプリを作ってみました。
画面遷移にはgo_routerを使用しました。versionが新しすぎると不具合があるので、2個手前のちょっと古いpackageを使用しました。
今回参考にした情報はFlutter界隈では有名なアンドレアさんWebサイトです。
完成したコード
- アプリのディレクトリ構成
- serviceにproviderとgo_routerの設定ファイルを作成.
- uiに認証関係のauthとログイン後のページを表示するpageを作成.
lib
├── firebase_options.dart
├── main.dart
├── service
│ ├── firebase_provider.dart
│ └── router.dart
└── ui
├── auth
│ └── signup_page.dart
└── page
└── my_page.dart
アプリの実行とログインしているかしていないかで、画面遷移先を設定するmain.dart
main.dart
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_auth/firebase_options.dart';
import 'package:riverpod_auth/service/firebase_provider.dart';
import 'package:riverpod_auth/service/router.dart';
import 'package:riverpod_auth/ui/auth/signup_page.dart';
import 'package:riverpod_auth/ui/page/my_page.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(
const ProviderScope(child: MyApp()),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: router,
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
);
}
}
// go_routerで最初に呼び出されるページ。何も表示されることはない.
// ログインしていなければ認証のページに移動し、ログインして入れば、
// ログイン後のページへ移動する.
class HomePage extends ConsumerWidget {
const HomePage({Key? key}) : super(key: key);
Widget build(BuildContext context, WidgetRef ref) {
// StreamProvider を監視し、AsyncValue<User?> を取得する。
final authStateAsync = ref.watch(authStateChangesProvider);
// パターンマッチングを使用して、状態をUIにマッピングする
return authStateAsync.when(
data: (user) => user != null ? MyPage() : SignUpPage(),
loading: () => const CircularProgressIndicator(),
error: (err, stack) => Text('Error: $err'),
);
}
}
Providerとgo_routerの設定をするserviceディレクトリのファイル
FirebaseとTextEditingControllerの設定
service/provider.dart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
final authStateChangesProvider = StreamProvider.autoDispose<User?>((ref) {
// 以下のプロバイダからFirebaseAuthを取得します。
final firebaseAuth = ref.watch(firebaseAuthProvider);
// Stream<User?> を返すメソッドを呼び出す。
return firebaseAuth.authStateChanges();
});
// プロバイダを使用して、FirebaseAuth インスタンスにアクセスします。
final firebaseAuthProvider = Provider<FirebaseAuth>((ref) {
return FirebaseAuth.instance;
});
// メールアドレスのテキストを保存するProvider
final emailProvider = StateProvider.autoDispose((ref) {
return TextEditingController(text: '');
});
final passwordProvider = StateProvider.autoDispose((ref) {
// パスワードのテキストを保存するProvider
return TextEditingController(text: '');
});
go_routerでルーティングの設定。
変数を_routerと書くとPrivateな変数になるからなのか、他のファイルで呼び出せなくなる!
service
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:riverpod_auth/ui/auth/signup_page.dart';
import 'package:riverpod_auth/ui/page/my_page.dart';
import '../main.dart';
/// GoRouterの画面遷移の設定
/// 変数は、_routerとすると、
/// privateになるからか、他のファイルで呼び出せなかった!
final GoRouter router = GoRouter(
routes: <GoRoute>[
/// 最初のページのページへ移動する設定
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) => const HomePage(),
routes: <GoRoute>[
/// パスワードをリセットするページへ画面遷移する設定
GoRoute(
path: 'auth',
builder: (BuildContext context, GoRouterState state) =>
const SignUpPage(),
),
/// ログイン後のページへ画面遷移する設定
GoRoute(
path: 'mypage',
builder: (BuildContext context, GoRouterState state) =>
const MyPage(),
),
],
),
],
);
アプリの画面のuiディレクトリ
認証機能を使用するページ。
今回は、新規登録とログインページ分けてません!
すいません🙇♂️
page/auth/signup_dart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:riverpod_auth/service/firebase_provider.dart';
/// 認証のページ.
/// 今回は新規登録とログインは同じページにしました.
class SignUpPage extends ConsumerWidget {
const SignUpPage({Key? key}) : super(key: key);
Widget build(BuildContext context, WidgetRef ref) {
final emailController = ref.watch(emailProvider);
final passwordlController = ref.watch(passwordProvider);
return Scaffold(
appBar: AppBar(
centerTitle: true, // AndroidのAppBarの文字を中央寄せ.
automaticallyImplyLeading: false, //戻るボタンを消す.
title: Text('新規登録'),
),
body: Center(
child: Container(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextField(
controller: emailController,
decoration: const InputDecoration(labelText: 'メールアドレス'),
),
TextField(
controller: passwordlController,
decoration: const InputDecoration(labelText: 'パスワード'),
obscureText: true,
),
ElevatedButton(
child: const Text('ユーザ登録'),
onPressed: () async {
try {
final User? user = (await FirebaseAuth.instance
.createUserWithEmailAndPassword(
email: emailController.text,
password: passwordlController.text))
.user;
if (user != null) {}
} catch (e) {
print(e);
}
},
),
ElevatedButton(
child: const Text('ログイン'),
onPressed: () async {
try {
// メール/パスワードでログイン
final User? user = (await FirebaseAuth.instance
.signInWithEmailAndPassword(
email: emailController.text,
password: passwordlController.text))
.user;
if (user != null) context.go('/mypage');
} catch (e) {
print(e);
}
},
),
],
),
),
),
);
}
}
ログイン後の画面を表示するmypage
ui/page/my_page.dart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
// Login後のページ
class MyPage extends ConsumerWidget {
const MyPage({Key? key}) : super(key: key);
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
actions: <Widget>[
IconButton(
icon: Icon(Icons.logout),
onPressed: () async {
// ログアウト処理
// 内部で保持しているログイン情報等が初期化される
await FirebaseAuth.instance.signOut();
context.go('/auth');
},
),
],
title: Text('HomePage'),
),
body: Text('Welcome! Login状態!'),
);
}
}
スクリーンショット
- 最初の画面
- 新規登録してログインする
ログイン後の画面
アプリを停止して、Runして再起動すると、ログインの状態を維持できていた!
最後に
技術のキャッチアップが遅れていたので、Riverpod + go_routerを組み合わせた技術構成でアプリが最近まで作れませんでした!
というより、新しいものばかりに手を出しすぎるとサポートされなくなって、代替手段を探さないといけないので、リスクを考えると怖くて個人開発では使わなかったですね。
Discussion