Flutter+FirebaseでサクッとGoogle認証を導入する

注意事項

この記事は、Notion AIに対して

  • 問題と解決策の概要
  • 例示用のコード

を渡して生成した実験的な記事になります。

はじめに

FlutterとFirebaseを使用してGoogle認証を導入する方法について説明します。
Google認証を導入することで、ユーザーが簡単にアプリにログインできるようになります。
本記事では、FlutterとFirebaseを使用してGoogle認証を導入するためのステップバイステップの手順をご紹介します。

事前準備

事前に以下のツールがインストールされていることが前提です。

アプリの画面構成

今回作るアプリの画面は2つで、それぞれの画面は以下のように定義しておきます。

  • Login
    • 未ログインであれば表示する画面
    • ログインボタンがあり、ログインが成功したらHome画面へ遷移する
  • Home
    • ログイン済みであれば表示する画面
    • ログアウトボタンがあり、ログアウトが成功したらLogin画面へ遷移する

Google認証を導入したFlutterアプリの作成

Firebaseプロジェクトの作成

Firebaseプロジェクトを作成し、Google認証できるように準備します。

  1. Firebase Consoleにログインし、プロジェクトを作成する
  2. Authenticationを有効化する
  3. Sign-in methodGoogleを有効化にする

Flutterプロジェクトの作成

Flutterアプリのためにプロジェクトを作成します。

  1. 新しいFlutterプロジェクトを悪性する

     flutter create flutter-auth-sample
    

FlutterFireのセットアップ

FlutterでFirebaseを使えるようにするためのCLIツールFlutterFire CLIをインストールします。

  1. FlutterFire CLIをグローバルにインストールする

     dart pub global activate flutterfire_cli
    
  2. FlutterFire CLIを使用して設定する

     fluterfire configure
    
  3. Firebase Authenticationを使用するために必要なライブラリを追加する

     flutter pub add firebase_core firebase_auth 
    

Firebaseの初期化処理の追加

Flutterアプリの起動時にFirebaseの初期化処理を追加します。

  1. Firebase用のパッケージを追加する

     import 'package:firebase_core/firebase_core.dart';
    
  2. main関数を非同期にし、Firebaseの初期化処理を行う

     // Before
     void main() {
       runApp(const MyApp());
     }
    
     // After
     Future<void> main() async {
       WidgetsFlutterBinding.ensureInitialized();
       await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
       await Firebase.initializeApp();
       runApp(const MyApp());
     }
    

Login画面の作成

Login画面を作成します。 この画面は、以下のような要件が存在します。

  • Login
    • 未ログインであれば表示する画面
    • ログインボタンがあり、ログインが成功したらHome画面へ遷移する

今の段階では、ボタンは配置のみだけにしログイン処理の実装は除外します。

  1. Login画面のWidgetを作成する

     class LoginPage extends StatefulWidget {
       static String routeName() => '/login';
    
       const LoginPage({super.key});
    
       @override
       State<LoginPage> createState() => _LoginPageState();
     }
    
     class _LoginPageState extends State<LoginPage> {
       @override
       Widget build(BuildContext context) {
         return Scaffold(
           appBar: AppBar(title: const Text('Login Page')),
           body: Center(
             // Google Sign-in
             child: ElevatedButton(
               child: const Text('Google Sign-in'),
               onPressed: () {},
             ),
           ),
         );
       }
     }
    
  2. アプリ起動時にLogin画面が表示されるようにMyAppを書き換える

     class MyApp extends StatelessWidget {
       const MyApp({super.key});
    
       // This widget is the root of your application.
       @override
       Widget build(BuildContext context) {
         return MaterialApp(
           title: 'Authentication Demo',
           debugShowCheckedModeBanner: false,
           theme: ThemeData.dark(),
           initialRoute: LoginPage.routeName(),
           routes: {
             LoginPage.routeName(): (context) => const LoginPage(),
           },
         );
       }
     }
    
  3. アプリを起動してエラーが出ずに表示されることを確認する

Home画面の作成

Home画面を作成します。 この画面は、以下のような要件が存在します。

  • Home
    • ログイン済みであれば表示する画面
    • ログアウトボタンがあり、ログアウトが成功したらLogin画面へ遷移する

今の段階では、ボタンは配置のみだけにしログアウト処理の実装は除外します。

  1. Home画面のWidgetを作成する

     class HomePage extends StatefulWidget {
       static String routeName() => '/home';
    
       const HomePage({super.key});
    
       @override
       State<HomePage> createState() => _HomePageState();
     }
    
     class _HomePageState extends State<HomePage> {
       @override
       Widget build(BuildContext context) {
         return Scaffold(
           appBar: AppBar(title: const Text('Home Page')),
           body: Center(
             child: ElevatedButton(
               child: const Text('Logout'),
               onPressed: () {},
             ),
           ),
         );
       }
     }
    
  2. アプリ起動時にLogin画面が表示されるようにMyAppを書き換える

     class MyApp extends StatelessWidget {
       const MyApp({super.key});
    
       // This widget is the root of your application.
       @override
       Widget build(BuildContext context) {
         return MaterialApp(
           title: 'Authentication Demo',
           debugShowCheckedModeBanner: false,
           theme: ThemeData.dark(),
           initialRoute: HomePage.routeName(), // <- ここを書き換え
           routes: {
             LoginPage.routeName(): (context) => const LoginPage(),
             HomePage.routeName(): (context) => const HomePage(), // <- 追記
           },
         );
       }
     }
    
  3. アプリを起動してエラーが出ずに表示されることを確認する

  4. initialRouteをLogin画面に戻しておく

     class MyApp extends StatelessWidget {
       const MyApp({super.key});
    
       // This widget is the root of your application.
       @override
       Widget build(BuildContext context) {
         return MaterialApp(
           title: 'Authentication Demo',
           debugShowCheckedModeBanner: false,
           theme: ThemeData.dark(),
           initialRoute: LoginPage.routeName(), // <- ここを書き換え
           routes: {
             LoginPage.routeName(): (context) => const LoginPage(),
             HomePage.routeName(): (context) => const HomePage(),
           },
         );
       }
     }
    

ログイン・ログアウト処理を実装する

Login画面とHome画面にあるボタンの実装をしていきます。

  1. ログイン・ログアウトを処理を行うクラスを追加する

     class AuthenticationProvider {
       static bool isSignedIn() {
         return FirebaseAuth.instance.currentUser != null;
       }
    
       static Future<User?> signIn() async {
         final FirebaseAuth auth = FirebaseAuth.instance;
         final GoogleAuthProvider authProvider = GoogleAuthProvider();
         final UserCredential userCredential =
             await auth.signInWithPopup(authProvider);
         return userCredential.user;
       }
    
       static Future<void> signOut() async {
         final FirebaseAuth auth = FirebaseAuth.instance;
         await auth.signOut();
       }
     }
    
  2. ログインボタンのonPressedを書き換えて、ログインできるようにする

     onPressed: () {
       showDialog(
         context: context,
         builder: (_) => const Center(child: CircularProgressIndicator()),
       );
    
       AuthenticationProvider.signIn().then((user) {
         Navigator.of(context).pop();
         if (user == null) throw Exception('User is null');
    
         ScaffoldMessenger.of(context).showSnackBar(
           SnackBar(
             content: Text('Welcome ${user!.displayName}'),
           ),
         );
         Navigator.of(context).pushReplacementNamed(HomePage.routeName());
       }).catchError((error) {
         Navigator.of(context).pop();
         ScaffoldMessenger.of(context).showSnackBar(
           SnackBar(
             content: Text(error.toString()),
           ),
         );
       });
     },
    
  3. ログアウトボタンのonPressedを書き換えて、ログアウトできるようにする

     onPressed: () {
       showDialog(
         context: context,
         builder: (_) => const Center(child: CircularProgressIndicator()),
       );
    
       AuthenticationProvider.signOut().then((_) {
         Navigator.of(context).pop();
         Navigator.of(context).pushReplacementNamed(LoginPage.routeName());
       }).catchError((error) {
         Navigator.of(context).pop();
         ScaffoldMessenger.of(context).showSnackBar(
           SnackBar(
             content: Text(error.toString()),
           ),
         );
       });
     },
    
  4. アプリを起動してLogin画面とHome画面をログインの状態によって切り替えれることを確認する

アプリ起動時にログイン状態によって表示する画面を切り替える

ログインしたままの状態で画面を開き直すと、常にLogin画面が表示されている状態です。
そのため、ログイン済みであればHome画面を表示するように書き換えます。

  1. initialRouteで分岐させて初期表示の画面を切り替える

     class MyApp extends StatelessWidget {
       const MyApp({super.key});
    
       // This widget is the root of your application.
       @override
       Widget build(BuildContext context) {
         return MaterialApp(
           title: 'Authentication Demo',
           debugShowCheckedModeBanner: false,
           theme: ThemeData.dark(),
           // isSignedIn()の状態で初期表示の画面を切り替える
           initialRoute: AuthenticationProvider.isSignedIn()
               ? HomePage.routeName()
               : LoginPage.routeName(),
           routes: {
             LoginPage.routeName(): (context) => const LoginPage(),
             HomePage.routeName(): (context) => const HomePage(),
           },
         );
       }
     }
    

おわりに

以上が、FlutterとFirebaseを使用してGoogle認証を導入するための手順となります。
各手順の詳しい説明は、上記の章を参照してください。実際に手順に従って、アプリを作成してみてください。