3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

FlutterでFirebaseを使用するサンプル - Authentication編

Last updated at Posted at 2022-10-14

試した全機能を一つの記事にすると長くなりそうなので、機能ごとに分けたいと思います。
本記事は、Firebase Authenticationについての記事です。
他の記事は以下に掲載しています

  • Cloud Firesotre(Comming soon)
  • Cloud Functions(Comming soon)
  • Firebase Cloud Messaging(Comming soon)
  • Cloud Storage(Comming soon)
  • Cloud Analytics(Comming soon)

はじめに

FlutterFirebaseを使用する勉強をしたので、、
サンプルアプリ的な感じで試せるコードを書いていきたいと思います。
目次の上から順にサンプルコードをコピペ実装していただければ動作すると思います。

Firebaseの環境構築とFirebaseの各機能の環境構築方法については、この記事では書きません。
環境構築はして、ドキュメントを読みながら試そうと思ったけど、
サンプルアプリ的な感じで動作を試すのに、コード書くのめんどくさいなって方や、
ドキュメントで使い方の説明書いてあるけど、実際どう使えばいいかよくわからないと挫折した方向けになります。
UIとセットで簡単にFirebaseの機能を試せるようにするというのが本記事の目的ですので、
勉強がてらさくっと試してみたい方に、コードのコピペで試せるように書いていきたいと思います。
またサンプルコードのポイントも解説していますので、ドキュメントで挫折した方の助けにもなるかと思います。

本記事では、Firebase Authenticationの機能のうち以下を試せるサンプルコードを掲載します。

  • ユーザー登録
  • サインイン
  • サインアウト
  • ユーザー情報の編集

目次

  1. エントリポイント
  2. 認証状態によって、サインイン前のページとサインイン後のページを切り替える
  3. サインインとユーザー登録
  4. ユーザー情報の取得とサインアウト
  5. ユーザー情報の編集

エントリポイント

サンプルアプリのエントリポイントは下記の実装にしています。
各機能の説明は、MyHomePage以下のコードを掲載しています。

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  runApp(const 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: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

認証状態によって、サインイン前のページとサインイン後のページを切り替える

認証状態によって、サインイン前のページとサインイン後のページを切り替える

【コードとポイント解説】

エントリポイントから最初に表示される画面であるMyHomePagaは以下のコードです。
このコードにより、
認証状態が未サインインの場合は、サインイン(とユーザー登録)画面
認証状態がサインイン済みの場合は、ユーザー情報を表示する画面を表示しています。

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});
  
  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return StreamBuilder<User?>(
      //stream: FirebaseAuth.instance.authStateChanges(),
      stream: FirebaseAuth.instance.userChanges(),
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return const SizedBox();
        }
        if (snapshot.hasData) {
          // Userデータがあるので、ユーザー情報表示画面へ
          return UserPage(user: snapshot.data);
        }
        // Userデータがないので、サインイン画面へ
        return const FirebaseSingInAndRegisterPage();
      },
    );
  }
}

ポイントは、StreamBuilderを使用し、サインインの状態によって表示するページを切り替えている点です。
サインインの状態は、FirebaseAuth.instance.userChanges()で確認できます。
これはStreamになるので、StreamBuilderstreamに指定しておいて、
サインインの状態が変わったタイミングで適切なページを表示できるようにしています。
コメントアウトしていますが、サインインの状態はFirebaseAuth.instance.authStateChanges()でも確認できます。
しかし、こちらはユーザーの情報編集時(名前やメールアドレスを変えたとき)に、通知が来ないため、
本サンプルではFirebaseAuth.instance.userChanges()を使っています。

認証状態の取得の詳細についてはこちらを参照ください

【サインインの状態の保持について】

一度、サインインするとサインインの状態は保持されますので、アプリを再起動してもサインインしたままとなります。
サインインの状態はデバイスのキャッシュとして保持される仕組みのようです。
もし、キャッシュを消したい場合は、設定→アプリ一覧→該当アプリを選択し、キャッシュとデータを削除すれば、
再起動時、未サインインの状態となります。
※Androidのみ確認済みです。iPhoneは未確認です。あと、画面の遷移は大体なので実際と若干異なるかもです。
サインインの状態保持についてはこちらを参照

サインインとユーザー登録

サインインとユーザー登録を行うページのサンプルコードは以下です。

class FirebaseSingInAndRegisterPage extends StatefulWidget {
  const FirebaseSingInAndRegisterPage({Key? key}) : super(key: key);

  @override
  State<FirebaseSingInAndRegisterPage> createState() => _FirebaseSingInAndRegisterPageState();
}

class _FirebaseSingInAndRegisterPageState extends State<FirebaseSingInAndRegisterPage> {
  bool _isStateSignIn = true;
  late TextEditingController _emailTextController;
  late TextEditingController _passwordTextController;

  @override
  void initState() {
    _emailTextController = TextEditingController();
    _passwordTextController = TextEditingController();
    super.initState();
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Login"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.all(8),
              child: TextField(
                controller: _emailTextController,
                decoration: const InputDecoration(
                  border: OutlineInputBorder(),
                  hintText: "Enter your email",
                ),
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(8),
              child: TextField(
                controller: _passwordTextController,
                decoration: const InputDecoration(
                  border: OutlineInputBorder(),
                  hintText: "Enter your password",
                ),
              ),
            ),
            ElevatedButton(
              onPressed: () =>
              _isStateSignIn ? _onSingIn(context) : _onRegister(context),
              child: Text(
                _isStateSignIn ? "Sign in" : "Register",
              ),
            ),
            TextButton(
                onPressed: () {
                  setState(() {
                    _isStateSignIn = !_isStateSignIn;
                  });
                },
                child: Text(_isStateSignIn ? "Register?" : "Sing in?"))
          ],
        ),
      ),
    );
  }

  Future<void> _onSingIn(BuildContext context) async {
    try {
      final userCredential =
      await FirebaseAuth.instance.signInWithEmailAndPassword(
        email: _emailTextController.text,
        password: _passwordTextController.text,
      );
    } catch (e) {
      _showErrorDialog(context, e.toString());
    }
  }

  Future<void> _onRegister(BuildContext context) async {
    try {
      final userCredential =
      await FirebaseAuth.instance.createUserWithEmailAndPassword(
        email: _emailTextController.text,
        password: _passwordTextController.text,
      );
    } catch (e) {
      _showErrorDialog(context, e.toString());
    }
  }

  void _showErrorDialog(BuildContext context, String error) {
    showDialog(
      context: context,
      builder: (_) {
        return AlertDialog(
          title: const Text("Error"),
          content: Text(error),
          actions: <Widget>[
            TextButton(
              child: Text("OK"),
              onPressed: () => Navigator.pop(context),
            ),
          ],
        );
      },
    );
  }
}

ポイントを解説します。
サインインは、_onSingInメソッドで実装しています。
サインインは、FirebaseAuth.instance.signInWithEmailAndPasswordを呼ぶと実行されます。
その名の通り、Eメールとパスワードでサインインできます。
認証方法は他にもあるようですが、この記事では割愛します。
詳しくは公式のドキュメントを参照お願いします。

続いて、ユーザー登録は_onRegisterメソッドで実装しています。
ユーザー登録は、FirebaseAuth.instance.createUserWithEmailAndPasswordを呼ぶと実行されます。
その名の通り、Eメールとパスワードでユーザー登録します。
すでに登録済みのEメールを指定した場合エラーとなります。

ユーザー情報の取得とサインアウト

ユーザー情報の取得とサインアウトを行うページのサンプルコードは以下です。
※コピペして動作させる場合、このコードだけだとエラーになるので、次の章のコードもコピーしてください。

class UserPage extends StatelessWidget {
  final User? user;
  const UserPage({Key? key, required this.user}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    if(user != null){
      return Scaffold(
        appBar: AppBar(actions: [
          IconButton(
            onPressed: () {
              Navigator.of(context).push(
                MaterialPageRoute(builder: (context) => const EditUserPage(),
                ),
              );
            },
            icon: const Icon(Icons.edit),
          ),
        ],),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [
              Text(user?.displayName ?? "Dispaly name is none"),
              Text(user?.email ?? "Email is none"),
              ElevatedButton(
                onPressed: () => FirebaseAuth.instance.signOut(),
                child: const Text("Sign out"),
              )
            ],
          ),
        ),
      );
    }

    return const Scaffold(
      body: Center(
        child: Text("No User"),
      ),
    );
  }
}

ポイントを解説します。

【ユーザー情報の取得】

ユーザーの情報は、Userクラスに入っています。
Userクラスには表示名やEメール、電話番号など設定情報を取得できます。
具体的にはuser?.displayNameuser?.emailで取得可能です。
ユーザー登録した時点では表示名や電話番号などは未設定でnullとなっています。

サインインしているUserは、 認証状態によって、サインイン前のページとサインイン後のページを切り替えるで説明したFirebaseAuth.instance.authStateChanges()FirebaseAuth.instance.userChanges()Streamで取得できます。
また、Streamを使わない方法であれば、FirebaseAuth.instance.currentUserでも取得できます。

【サインアウト】

サインアウトはFirebaseAuth.instance.signOut()でできます。
サンプルコードは雑に書いて例外処理してませんが、未サインインの状態だと例外出るかもしれません。
(本サンプルコードでは、未サインインの状態でこのページに遷移することはないので心配ありませんが、ちゃんと書くなら例外処理はした方が良いです)

ユーザー情報の編集

class _EditUserPageState extends State<EditUserPage> {
  late TextEditingController _displayNameController;
  late TextEditingController _emailController;

  @override
  void initState() {
    final currentUser = FirebaseAuth.instance.currentUser;
    final initDisplayName = currentUser?.displayName ?? "";
    final initEmail = currentUser?.email ?? "";
    _displayNameController = TextEditingController(text: initDisplayName);
    _emailController  = TextEditingController(text: initEmail);
    super.initState();
  }

  @override
  void dispose() {
    _displayNameController.dispose();
    _emailController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Edit user"),
      ),
      body: SafeArea(
        child: Column(
          children: [
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: TextField(
                controller: _displayNameController,
                decoration: const InputDecoration(
                  hintText: "Enter your display name",
                  border: OutlineInputBorder(),
                ),
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: TextField(
                controller: _emailController,
                decoration: const InputDecoration(
                  hintText: "Enter your email",
                  border: OutlineInputBorder(),
                ),
              ),
            ),
            ElevatedButton(
              onPressed: () => _save(context),
              child: const Text("Save"),
            ),
          ],
        ),
      ),
    );
  }

  Future<void> _save(BuildContext context) async {
    final currentUser = FirebaseAuth.instance.currentUser;
    if (currentUser == null) {
      _showErrorDialog(context, "Current user is null.");
    }

    try {
      final currentDisplayName = currentUser?.displayName ?? "";
      if (currentDisplayName != _displayNameController.text) {
        await currentUser?.updateDisplayName(_displayNameController.text);
      }

      final currentEmail = currentUser?.email ?? "";
      if (currentEmail != _emailController.text) {
        await currentUser?.updateEmail(_emailController.text);
      }

      if (!mounted) return;
      Navigator.of(context).pop();
    } catch (e) {
      _showErrorDialog(context, e.toString());
    }
  }

  void _showErrorDialog(BuildContext context, String error) {
    showDialog(
      context: context,
      builder: (_) {
        return AlertDialog(
          title: const Text("Error"),
          content: Text(error),
          actions: <Widget>[
            TextButton(
              child: Text("OK"),
              onPressed: () => Navigator.pop(context),
            ),
          ],
        );
      },
    );
  }
}

ポイントを解説します。

【ユーザー情報の編集】

ユーザー情報は_saveメソッド内で実装しています。
具体的にはawait currentUser?.updateDisplayName(_displayNameController.text);
await currentUser?.updateEmail(_emailController.text);です。
情報の編集は、Userクラスのupdate~というメソッドで編集可能です。
情報を編集すると、FirebaseAuth.instance.userChanges()で通知されるので、
編集後の情報のUserが取得でき、UserPageが再描画されます。

さいごに

Authenticationについては以上です。
最初、公式ドキュメントで勉強したとき、サインイン(ユーザー登録) → 情報表示 → サインアウトという一連の流れが、
コードとしてまとまってなかったので、とっかかりにくい印象を受けました。
ただ今回の記事でまとめてみたので、最初の一歩として参考になれば幸いです。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?