Flutterから利用できるFirebaseサービスをカウンターアプリで実践(Authentication編)
はじめに
Flutterから利用できるFirebaseの機能をカウンターアプリに実装し、内容をまとめたシリーズの第5回目です。
今回はユーザー認証機能を使うことができるFirebase Authenticationについてです。
目次
シリーズの内容
回数 | 内容 | リンク |
---|---|---|
第0回 | 準備編 | ブログ |
第1回 | Analytics |
ブログ |
第2回 | Firebase Crashlytics |
ブログ |
第3回 | Firebase Performance Monitoring |
ブログ |
第4回 | Firebase Remote Config |
ブログ |
第5回 | Firebase Authentication |
イマココ |
第6回 | Cloud Firestore |
ブログ |
第7回 | Firebase Realtime Database |
ブログ |
第8回 | Cloud Storage for Firebase |
ブログ |
第9回 | Firebase Cloud Messaging |
ブログ |
第10回 | Firebase In-App Messaging |
ブログ |
第11回 | Firebase ML |
ブログ |
第12回 | Cloud Functions for Firebase |
ブログ |
第13回 | Firebase Hosting |
ブログ |
第14回 | Firebaseのその他のサービス | ブログ |
開発環境
項目 | 内容 |
---|---|
PC | Macbook Air(M1) |
Flutter | 3.0.1 |
Firebase | 11.0.1 |
FlutterFire | 0.2.2+2 |
デバッグデバイス | Android 12(APIレベル31) , Chrome |
準備
準備編が完了出来ているものとします。
導入方法
プロジェクトにfirebase_authを導入するため、pubspec.yaml
に追記しインポートします。
dependencies: firebase_auth: ^3.3.18
ログインの方法(メールアドレス、電話番号など)を選択するため、Firebase ConsoleからAuthentication
へ移動し、Sign-in Method
で希望するログイン方法を選びます。
今回は、メールアドレスとパスワードでの設定を行います。
Firebase Consoleでの設定が終わったら、実装していきます。
TextField
に入力したメールアドレスとパスワードを入力します。
パスワードはobscureText
をtrue
にして、見えないようにします。
/// メールアドレス入力 TextField( decoration: const InputDecoration( label: Text('E-mail'), ), controller: _idController, ), /// パスワード入力 TextField( decoration: const InputDecoration( label: Text('Password'), ), controller: _passController, obscureText: true, ),
実行ボタンを作り、アカウント作成またはサインインできる関数を呼びます。
/// アカウント作成の場合 Container( margin: const EdgeInsets.all(10), child: ElevatedButton( onPressed: () { _createAccount(ref, idController.text, passController.text); }, child: const Text('アカウント作成'), ), ),
アカウント作成の処理は、FirebaseAuth.instance.createUserWithEmailAndPassword
を使います。
メールアドレスとパスワードを渡し、エラーが発生したらエラーメッセージを出すようにします。
import 'package:firebase_auth/firebase_auth.dart'; void _createAccount(String id, String pass) async { try { /// credential にはアカウント情報が記録される final credential = await FirebaseAuth.instance.createUserWithEmailAndPassword( email: id, password: pass, ); } /// アカウントに失敗した場合のエラー処理 on FirebaseAuthException catch (e) { /// パスワードが弱い場合 if (e.code == 'weak-password') { print('パスワードが弱いです'); /// メールアドレスが既に使用中の場合 } else if (e.code == 'email-already-in-use') { print('すでに使用されているメールアドレスです'); } /// その他エラー else { print('アカウント作成エラー'); } } catch (e) { print(e); } }
サインインの処理は、FirebaseAuth.instance.signInWithEmailAndPassword
を使います。
メールアドレスとパスワードを渡し、エラーが発生したらエラーメッセージを出すようにします。
void _signIn(String id, String pass) async { try { /// credential にはアカウント情報が記録される final credential = await FirebaseAuth.instance.signInWithEmailAndPassword( email: id, password: pass, ); } /// サインインに失敗した場合のエラー処理 on FirebaseAuthException catch (e) { /// メールアドレスが無効の場合 if (e.code == 'invalid-email') { print('メールアドレスが無効です'); } /// ユーザーが存在しない場合 else if (e.code == 'user-not-found') { print('ユーザーが存在しません'); } /// パスワードが間違っている場合 else if (e.code == 'wrong-password') { print('パスワードが間違っています'); } /// その他エラー else { print('サインインエラー'); } } }
サインアウトはFirebaseAuth.instance.signOut()
を使用します。
void _signOut() async { await FirebaseAuth.instance.signOut(); }
ユーザーの情報を取得するためには、3通りの方法があります。
/// authStateChanges、idTokenChanges、およびuserChangesストリームを使う FirebaseAuth.instance .authStateChanges() .listen((User? user) { if (user != null) { print(user.uid); } }); /// 認証(signIn)メソッドによって返されるUserCredentialオブジェクトを使う final userCredential = await FirebaseAuth.instance.signInWithCredential(credential); final user = userCredential.user; print(user?.uid); /// FirebaseAuthインスタンスのcurrentUserプロパティを使う if (FirebaseAuth.instance.currentUser != null) { print(FirebaseAuth.instance.currentUser?.uid); }
ユーザーのプロファイルやメールアドレスを更新するには、.update***
を使用します。
final userCredential = await FirebaseAuth.instance.signInWithCredential(credential); final user = userCredential.user; await user?.updateDisplayName("Jane Q. User"); await user?.updateEmail("janeq@example.com");
メールアドレスでの認証だけではなく、電話番号での認証や、OAuthでの認証もあります。
サインイン前のホーム画面
認証画面
サインイン後のホーム画面
コード全文
前回からの変更点です。
Firebase Auth
を実装- サインページを追加
- その他、コードの変更
main.dart
/// Flutter関係のインポート import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'dart:async'; /// Firebase関係のインポート import 'package:firebase_core/firebase_core.dart'; import 'firebase_options.dart'; import 'package:firebase_analytics/firebase_analytics.dart'; import 'package:firebase_crashlytics/firebase_crashlytics.dart'; import 'package:firebase_auth/firebase_auth.dart'; /// 他ページのインポート import 'package:counter_firebase/normal_counter_page.dart'; import 'package:counter_firebase/crash_page.dart'; import 'package:counter_firebase/auth_page.dart'; import 'package:counter_firebase/remote_config_page.dart'; /// メイン void main() async { /// クラッシュハンドラ runZonedGuarded<Future<void>>(() async { /// Firebaseの初期化 WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform, ); /// クラッシュハンドラ(Flutterフレームワーク内でスローされたすべてのエラー) FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError; /// runApp w/ Riverpod runApp(const ProviderScope(child: MyApp())); }, /// クラッシュハンドラ(Flutterフレームワーク内でキャッチされないエラー) (error, stack) => FirebaseCrashlytics.instance.recordError(error, stack, fatal: true)); } /// Providerの初期化 /// カウンター用のプロバイダー final counterProvider = StateNotifierProvider.autoDispose<Counter, int>((ref) { return Counter(); }); class Counter extends StateNotifier<int> { Counter() : super(0); /// カウントアップ void increment() => state++; } /// MaterialAppの設定 class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( title: 'Counter Firebase', theme: ThemeData( primarySwatch: Colors.blue, ), home: const MyHomePage(), debugShowCheckedModeBanner: false, ); } } /// ホーム画面 class MyHomePage extends ConsumerWidget { const MyHomePage({Key? key}) : super(key: key); @override Widget build(BuildContext context, WidgetRef ref) { FirebaseAuth.instance.authStateChanges().listen((User? user) { if (user == null) { ref.watch(userEmailProvider.state).state = 'ログインしていません'; } else { ref.watch(userEmailProvider.state).state = user.email!; } }); return Scaffold( appBar: AppBar( title: const Text('My Homepage'), ), body: ListView( padding: const EdgeInsets.all(10), children: <Widget>[ /// ユーザ情報の表示 Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.person), Text(ref.watch(userEmailProvider)), ], ), /// 各ページへの遷移 _PagePushButton(context, 'ノーマルカウンター', const NormalCounterPage()), _PagePushButton(context, 'クラッシュページ', const CrashPage()), _PagePushButton( context, 'Remote Configカウンター', const RemoteConfigPage()), _PagePushButton(context, '認証ページ', const AuthPage()), ], ), ); } } /// ページ遷移ボタン class _PagePushButton extends Container { _PagePushButton(BuildContext context, String buttonTitle, pagename) : super( padding: const EdgeInsets.all(10), child: ElevatedButton( child: Text(buttonTitle), onPressed: () { AnalyticsService().logPage(buttonTitle); Navigator.push( context, MaterialPageRoute(builder: (context) => pagename)); }, ), ); } /// Analyticsの実装 class AnalyticsService { /// ページ遷移のログ Future<void> logPage(String screenName) async { await FirebaseAnalytics.instance.logEvent( name: 'screen_view', parameters: { 'firebase_screen': screenName, }, ); } }
auth_page.dart
/// Flutter import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; /// Firebase関係のインポート import 'package:firebase_auth/firebase_auth.dart'; /// Other Page import 'package:counter_firebase/main.dart'; /// Authのサインイン状態のprovider final signInStateProvider = StateProvider((ref) => 'サインインまたはアカウントを作成してください'); /// サインインユーザーの情報プロバイダー final userProvider = StateProvider<User?>((ref) => null); final userEmailProvider = StateProvider<String>((ref) => 'ログインしていません'); /// ページ設定 class AuthPage extends ConsumerStatefulWidget { const AuthPage({Key? key}) : super(key: key); @override AuthPageState createState() => AuthPageState(); } class AuthPageState extends ConsumerState<AuthPage> { @override void initState() { super.initState(); /// 現在のユーザー情報 print(FirebaseAuth.instance.currentUser?.displayName); print(FirebaseAuth.instance.currentUser?.email); } @override Widget build(BuildContext context) { final singInStatus = ref.watch(signInStateProvider); final idController = TextEditingController(); final passController = TextEditingController(); return Scaffold( appBar: AppBar( title: const Text('Auth Page'), ), body: ListView( padding: const EdgeInsets.all(10), children: <Widget>[ /// メールアドレス入力 TextField( decoration: const InputDecoration( label: Text('E-mail'), icon: Icon(Icons.mail), ), controller: idController, ), /// パスワード入力 TextField( decoration: const InputDecoration( label: Text('Password'), icon: Icon(Icons.key), ), controller: passController, obscureText: true, ), /// サインイン Container( margin: const EdgeInsets.all(10), child: ElevatedButton( onPressed: () { /// ログインの場合 _signIn(ref, idController.text, passController.text); }, style: ButtonStyle( backgroundColor: MaterialStateProperty.all(Colors.grey)), child: const Text('サインイン'), ), ), /// アカウント作成 Container( margin: const EdgeInsets.all(10), child: ElevatedButton( onPressed: () { /// アカウント作成の場合 _createAccount(ref, idController.text, passController.text); }, child: const Text('アカウント作成'), ), ), /// ログイン状態の表示 Container( padding: const EdgeInsets.all(10), child: Text('メッセージ : $singInStatus'), ), /// サインアウト TextButton( onPressed: () { _signOut(ref); }, child: const Text('SIGN OUT')) ], ), ); } } /// サインイン処理 void _signIn(WidgetRef ref, String id, String pass) async { try { /// credential にはアカウント情報が記録される final credential = await FirebaseAuth.instance.signInWithEmailAndPassword( email: id, password: pass, ); /// ユーザ情報の更新 ref.watch(userProvider.state).state = credential.user; print(credential); /// 画面に表示 ref.read(signInStateProvider.state).state = 'サインインできました!'; } /// サインインに失敗した場合のエラー処理 on FirebaseAuthException catch (e) { /// メールアドレスが無効の場合 if (e.code == 'invalid-email') { ref.read(signInStateProvider.state).state = 'メールアドレスが無効です'; } /// ユーザーが存在しない場合 else if (e.code == 'user-not-found') { ref.read(signInStateProvider.state).state = 'ユーザーが存在しません'; } /// パスワードが間違っている場合 else if (e.code == 'wrong-password') { ref.read(signInStateProvider.state).state = 'パスワードが間違っています'; } /// その他エラー else { ref.read(signInStateProvider.state).state = 'サインインエラー'; } } } /// アカウント作成 void _createAccount(WidgetRef ref, String id, String pass) async { try { /// credential にはアカウント情報が記録される final credential = await FirebaseAuth.instance.createUserWithEmailAndPassword( email: id, password: pass, ); /// ユーザ情報の更新 ref.watch(userProvider.state).state = credential.user; print(credential); /// 画面に表示 ref.read(signInStateProvider.state).state = 'アカウント作成に成功しました!'; } /// アカウントに失敗した場合のエラー処理 on FirebaseAuthException catch (e) { /// パスワードが弱い場合 if (e.code == 'weak-password') { ref.read(signInStateProvider.state).state = 'パスワードが弱いです'; /// メールアドレスが既に使用中の場合 } else if (e.code == 'email-already-in-use') { ref.read(signInStateProvider.state).state = 'すでに使用されているメールアドレスです'; } /// その他エラー else { ref.read(signInStateProvider.state).state = 'アカウント作成エラー'; } } catch (e) { print(e); } } /// サインアウト void _signOut(WidgetRef ref) async { await FirebaseAuth.instance.signOut(); ref.read(signInStateProvider.state).state = 'サインインまたはアカウントを作成してください'; }
GitHubのページを貼ります。