Flutterアプリケーション開発概論

【ログイン処理】signInWithEmailAndPasswordでFirebase Authenticationにログインする

13LINE風チームタスク管理アプリを作りながら、ログイン・データベース・権限管理を学ぶ
FlutteriOSAndroidMacOSWindows基礎から学ぶ開発アプリ開発

このページでやること

このページでは、ログイン画面を作ります。

ログイン画面とは、メールアドレスとパスワードを入力して、アプリに入るための画面です。

このページでは、まだログイン処理の中身は深く説明しません。

まずは、次のような画面を作ることがゴールです。

WorkBoard

チームの会話のように、タスクを共有する

メールアドレス
パスワード

[ログイン]

新規登録はこちら

この教材では、いつも通り次の流れで進めます。

完成コードを使う
↓
一部分だけ変更する
↓
保存する・実行する
↓
画面の変化を見る
↓
次のステップへ進む

このページで出てくる単語

単語一言説明
LoginPageログイン画面を作るWidget
StatefulWidget画面の中で変わる値を持てるWidget
TextEditingController入力欄の文字を管理する道具
TextField文字を入力するための部品
obscureTextパスワードを見えにくくする設定
FilledButton塗りつぶし型のボタン
Navigator画面を移動するための仕組み
setState画面の表示を更新する命令
dispose使い終わった道具を片付ける処理

Widget とは、Flutterの画面部品のことです。


npmや環境変数はこのページで必要?

このページでは、npmは使いません。

npmとは、Node.jsのパッケージを管理する道具です。

環境変数も設定しません。

環境変数とは、パソコン全体で使う設定値のことです。

このページでやることは、これだけです。

main.dartを開く
↓
LoginPageを書く
↓
保存する
↓
flutter runで確認する

Step 1:main.dartを開く

Flutterプロジェクトの中で、次のファイルを開きます。

lib/main.dart

VS Codeを使っている場合は、ターミナルで次を実行してもOKです。

code lib/main.dart

code が使えない場合は、VS Codeの左側から lib/main.dart を開いてください。


Step 2:LoginPageを追加する

AuthGate の下あたりに、次のコードを追加します。

class LoginPage extends StatefulWidget {
  const LoginPage({super.key});

  @override
  State<LoginPage> createState() => _LoginPageState();
}

LoginPage はログイン画面です。

ここでは StatefulWidget を使います。

StatefulWidget とは、画面の中で変わる値を持てるWidgetです。

ログイン画面では、入力中のメールアドレス、パスワード、読み込み中かどうか、エラー文などが変わります。

だから StatefulWidget を使います。


Step 3:_LoginPageStateを追加する

続けて、次のコードを追加します。

class _LoginPageState extends State<LoginPage> {
  final emailController = TextEditingController();
  final passwordController = TextEditingController();

  bool isLoading = false;
  String? errorText;

  @override
  void dispose() {
    emailController.dispose();
    passwordController.dispose();
    super.dispose();
  }

  Future<void> login() async {
    setState(() {
      isLoading = true;
      errorText = null;
    });

    try {
      await FirebaseAuth.instance.signInWithEmailAndPassword(
        email: emailController.text.trim(),
        password: passwordController.text,
      );
    } on FirebaseAuthException catch (e) {
      setState(() {
        errorText = e.message ?? 'ログインに失敗しました。';
      });
    } catch (_) {
      setState(() {
        errorText = 'ログインに失敗しました。';
      });
    } finally {
      if (mounted) {
        setState(() {
          isLoading = false;
        });
      }
    }
  }

ここまでで、入力欄の準備とログイン処理の準備ができました。

emailController は、メールアドレス入力欄の文字を管理します。

passwordController は、パスワード入力欄の文字を管理します。

isLoading は、ログイン中かどうかを表します。

errorText は、ログインに失敗したときのエラー文です。

Future<void> は、時間がかかる処理を表す書き方です。


Step 4:ログイン処理を短く理解する

この部分がログイン処理です。

await FirebaseAuth.instance.signInWithEmailAndPassword(
  email: emailController.text.trim(),
  password: passwordController.text,
);

signInWithEmailAndPassword は、Firebase Authenticationでメールアドレスとパスワードを使ってログインする命令です。

trim() は、文字の前後にある余分な空白を消す処理です。

たとえば、メールアドレスの最後に空白が入っても、ログイン失敗しにくくなります。

入力されたメールアドレス
↓
余分な空白を消す
↓
Firebaseにログインを依頼する

Step 5:画面部分を追加する

先ほどの _LoginPageState の中に、続けて次のコードを入れます。

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: AppColors.bg,
      body: SafeArea(
        child: Center(
          child: SingleChildScrollView(
            padding: const EdgeInsets.all(20),
            child: ConstrainedBox(
              constraints: const BoxConstraints(maxWidth: 420),
              child: AppCard(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.stretch,
                  children: [
                    const Text(
                      'WorkBoard',
                      textAlign: TextAlign.center,
                      style: TextStyle(
                        color: AppColors.lineGreen,
                        fontSize: 32,
                        fontWeight: FontWeight.w900,
                      ),
                    ),
                    const SizedBox(height: 8),
                    const Text(
                      'チームの会話のように、タスクを共有する',
                      textAlign: TextAlign.center,
                      style: TextStyle(
                        color: AppColors.subText,
                        fontWeight: FontWeight.w600,
                      ),
                    ),
                    const SizedBox(height: 28),
                    AppTextField(
                      controller: emailController,
                      label: 'メールアドレス',
                      keyboardType: TextInputType.emailAddress,
                    ),
                    const SizedBox(height: 12),
                    AppTextField(
                      controller: passwordController,
                      label: 'パスワード',
                      obscureText: true,
                    ),
                    if (errorText != null) ...[
                      const SizedBox(height: 12),
                      ErrorBox(message: errorText!),
                    ],
                    const SizedBox(height: 20),
                    FilledButton(
                      onPressed: isLoading ? null : login,
                      child: Text(isLoading ? 'ログイン中...' : 'ログイン'),
                    ),
                    const SizedBox(height: 12),
                    TextButton(
                      onPressed: isLoading
                          ? null
                          : () {
                              Navigator.of(context).push(
                                MaterialPageRoute(
                                  builder: (_) => const RegisterPage(),
                                ),
                              );
                            },
                      child: const Text('新規登録はこちら'),
                    ),
                  ],
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

これでログイン画面のUIができます。

Scaffold は、Flutter画面の基本の土台です。

SafeArea は、スマホのノッチやステータスバーに画面が重ならないようにする部品です。

SingleChildScrollView は、画面が小さいときにスクロールできるようにする部品です。

ConstrainedBox は、横幅の最大サイズを決める部品です。


Step 6:画面部品の役割を見る

ログイン画面の中身は、次のように分かれています。

部品役割
Text('WorkBoard')アプリ名を表示する
AppTextFieldメールアドレスとパスワードを入力する
ErrorBoxエラーがあるときだけ表示する
FilledButtonログインボタン
TextButton新規登録画面へ移動するボタン

全部を一気に覚えなくて大丈夫です。

まずは、画面にどの順番で並んでいるかを見るだけでOKです。


Step 7:RegisterPageがまだない場合

このコードでは、次の部分で RegisterPage を使っています。

builder: (_) => const RegisterPage(),

RegisterPage とは、新規登録画面のことです。

まだ作っていない場合、エラーになります。

その場合は、一時的に仮の RegisterPage を追加してください。

class RegisterPage extends StatelessWidget {
  const RegisterPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('新規登録'),
      ),
      body: const Center(
        child: Text('新規登録画面は次のページで作ります'),
      ),
    );
  }
}

あとで本物の新規登録画面に差し替えます。

完成コードを使っている場合は、すでに RegisterPage があるので、この仮コードは不要です。


Step 8:TeamListPageがまだない場合

ログインに成功すると、AuthGateTeamListPage に切り替えます。

まだ TeamListPage がない場合は、仮の画面を置いてください。

class TeamListPage extends StatelessWidget {
  const TeamListPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('トーク'),
      ),
      body: const Center(
        child: Text('チーム一覧画面はあとで作ります'),
      ),
    );
  }
}

完成コードを使っている場合は不要です。


Step 9:保存する

main.dart を保存します。

Macの場合:

command + S

Windowsの場合:

Ctrl + S

保存しないと、変更が反映されません。


Step 10:実行する

ターミナルで実行します。

flutter run

すでに起動中の場合は、ターミナルで r を押します。

r

r はホットリロードです。

ホットリロードとは、アプリを起動したまま変更を反映する機能です。


Step 11:画面を確認する

次の画面が表示されればOKです。

WorkBoard

チームの会話のように、タスクを共有する

メールアドレス
パスワード

ログイン

新規登録はこちら

ここでは、見た目ができれば成功です。

まだユーザー登録していない場合は、ログインしてもエラーになります。

それで大丈夫です。

次のページ以降で、新規登録画面を作ります。


Step 12:一部分だけ変更してみる

試しに、サブタイトルを変更してみます。

この部分を探してください。

'チームの会話のように、タスクを共有する',

次のように変えてみます。

'チームでタスクを共有するアプリ',

保存して、ホットリロードします。

r

画面の文字が変われば成功です。

確認できたら、好きな表現に戻してOKです。


ログイン失敗を確認する

適当なメールアドレスとパスワードを入力して、ログインボタンを押してみます。

まだユーザーが登録されていなければ、エラーが表示されます。

ログインに失敗しました。

またはFirebaseからの英語メッセージが表示されることもあります。

エラーが赤い箱で表示されれば、ErrorBox が動いています。

これは成功です。


よくあるエラーと直し方

エラー原因直し方
FirebaseAuth isn't definedimportがないfirebase_auth.dart をimportする
AppTextField isn't defined共通UIがない前ページの AppTextField を追加する
AppCard isn't defined共通UIがない前ページの AppCard を追加する
ErrorBox isn't defined共通UIがない前ページの ErrorBox を追加する
RegisterPage isn't a class新規登録画面がまだない仮の RegisterPage を追加する
setState isn't definedStatefulWidget になっていないState<LoginPage> の中に書く
画面が変わらない保存していないcommand + S
ホットリロードで直らない状態が残っているR または再起動する

FirebaseAuth isn't defined** が出たとき**

main.dart の上に、次の1行があるか確認します。

import 'package:firebase_auth/firebase_auth.dart';

なければ追加してください。

保存して、もう一度実行します。

flutter run

RegisterPage** がないと言われたとき**

新規登録画面は、まだ後のページで作ります。

今は仮の画面でOKです。

class RegisterPage extends StatelessWidget {
  const RegisterPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('新規登録'),
      ),
      body: const Center(
        child: Text('新規登録画面は次のページで作ります'),
      ),
    );
  }
}

最短作業まとめ

読むのが大変な人は、ここだけ見てください。

1. lib/main.dartを開く
2. LoginPageを追加する
3. _LoginPageStateを追加する
4. 保存する
5. flutter run
6. ログイン画面が出るか確認する

実行コマンドです。

flutter run

起動中なら、

r

チェックリスト

□ LoginPageを作った
□ StatefulWidgetで作った
□ emailControllerを作った
□ passwordControllerを作った
□ login()を作った
□ AppCardを使った
□ AppTextFieldを2つ使った
□ FilledButtonを使った
□ TextButtonで新規登録画面に移動できるようにした
□ 保存した
□ flutter runでログイン画面を確認した

ミニ確認問題

Q1. LoginPageは何をする画面ですか?

回答

メールアドレスとパスワードを入力して、アプリにログインする画面です。


Q2. TextEditingControllerは何のために使いますか?

回答

入力欄に入力された文字を取得するために使います。

今回なら、メールアドレスとパスワードを取得します。


Q3. obscureText: true は何をしますか?

回答

パスワード入力欄の文字を見えにくくします。


Q4. このページでnpmや環境変数は必要ですか?

回答

必要ありません。

このページでは、main.dart にログイン画面のUIを作るだけです。


このページのまとめ

  • このページでは、メールアドレスとパスワードでログインするUIを作った。
  • LoginPage はログイン画面。
  • 入力欄の文字は TextEditingController で管理する。
  • パスワード入力欄には obscureText: true を使う。
  • ログインボタンには FilledButton を使う。
  • 新規登録画面へは Navigator で移動する。
  • エラーがあるときは ErrorBox で表示する。
  • このページではnpmや環境変数は不要。
  • まずは保存して、画面の変化を見ることが大切。

次のページでやること

次のページでは、ログインボタンを押したときの処理をもう少し詳しく見ます。

signInWithEmailAndPassword を使って、Firebase Authenticationにログインする流れを理解します。

教材トップへ戻る