flutter-codelab

author: 針猫 summary: この Codelab では、FlutterとFirebase及びその関連パッケージの使い方をToDoアプリ制作を通して学びます。 id: todo-app categories: flutter environments: Android status: Draft feedback link: https://github.com/gdsc-osaka/codelab-flutter-tutorial-todo/issues analytics account: 198538794

ToDoアプリ制作を通じて Flutter / Firebase / Riverpod を知る

はじめに

この Codelab では、次のような Todo アプリの作成を通して Firebase の基本的な使い方を学びます。

前提条件

Firebaseにログイン

Google アカウントで Firebase にログインし、新しいプロジェクトを作成します。

ステップ 0/3

Firebase にログインし、プロジェクトの作成をクリック Firebaseトップ

ステップ 1/3

プロジェクト名とプロジェクトIDを設定 プロジェクトの作成 1/3

ステップ 2/3

プロジェクトの作成 2/3

ステップ 3/3

アナリティクスの地域を日本に設定し、✓ を押下 プロジェクトの作成 3/3

GitHubからテンプレートプロジェクトをダウンロード

ここ から Todo アプリのテンプレートをダウンロードします。

ダウンロードしたZIPファイルを任意の場所に展開し、エディタまたは IDE でルートディレクトリを開きます。

なお、アプリのソースコードは GitHub で公開しています。

Firebaseのインストール

Flutter アプリに Firebase を追加する を参照してFirebaseをインストールします。

ステップ 1: 必要なコマンドライン ツールをインストールする

Riverpod の追加

Riverpod とは、Flutter 内で簡単に状態(State)を管理できるパッケージです。この Todo アプリでは、Riverpod を使用して Firebase などのデータを管理します。

Riverpod の追加

以下のコマンドを実行して、アプリに Riverpod を追加します。

flutter pub add riverpod
flutter pub get

Riverpod を使ってみる

Firebase Auth から得られるユーザーデータを Riverpod で管理してみましょう。 /lib/api/auth_providers.dart に以下のコードを追加します。

final userChangesProvider = StreamProvider<User?>((ref) => FirebaseAuth.instance.userChanges());

Firebase Authを追加

Firebase Authentication を使えば、メールアドレス/パスワード認証・電話番号認証・Google認証・各SNSの認証をクライアント(アプリ)のプログラムを書くだけで実装できます。



ステップ1: アプリに Firebase Authentication を追加する

  1. プロジェクトのルートから、ターミナルで以下のコマンドを実行してプラグインをインストールします。
     flutter pub add firebase_auth
    
  2. 以下のコマンドを実行してプラグインを取得します。
     flutter pub get
    



ステップ2: Firebase Console で Firebase Auth を有効化する

Firebase Console から、以下の手順の通りに認証を有効化します。

  1. Authentication を有効化 start auth

  2. メール認証を有効化 メール認証を有効化 1 メール認証を有効化 2

  3. Google認証を有効化 Google認証を有効化

Positive
メール・Google以外の認証プロバイダの追加については、各ドキュメントを参照してください。

次のセクションでは、アプリ内にユーザー認証を実装します。

メールログインを追加

このセクションでは、Flutter アプリにメールアドレス認証を追加します。

ステップ1: ログインコードを追加する

/lib/features/auth/email_login_page.dart を開き、36行目の signIn() にメールアドレス認証を追加します。

signIn() async {
  if (_formKey.currentState?.validate() ?? false) {
    // ...

    // Firebase Authを追加
    await FirebaseAuth.instance.signInWithEmailAndPassword(
      email: _email,
      password: _password,
    );
  }
}

次に、ログインに失敗したときの例外を追加します。

なお、ログインやユーザー登録など、実行する関数によってエラーコード (e.code) が異なります。 対応するエラーコードについては、[Flutter×Firebase] firebase_authのエラーハンドリング(例外処理、try-catch)を学ぶ などを参照してください。

signIn() async {
  try {
    await FirebaseAuth.instance.signInWithEmailAndPassword(
      email: _email,
      password: _password,
    );

  } on FirebaseAuthException catch (e) {
    if (e.code == 'user-not-found' || e.code == 'wrong-password') {
      // スナックバーを表示
      messenger.showSnackBar(const SnackBar(content: Text('ユーザー名またはパスワードが不明です')));
    }
    
  } catch (e) {
    // デバッグ時のみエラーコードを出力
    if (kDebugMode) {
      print(e);
    }
  }
}

ステップ2: LoaderOverlay を表示・非表示する

認証ボタンが押下されたときに Loading のオーバーレイが表示されるコードを追加します。
なお、以降で省略できる個所は ... と記述します。

signIn() async {
  if (...) {
    // LoaderOverlayを追加
    context.loaderOverlay.show();
    final messenger = ScaffoldMessenger.of(context);

    try {
      // ...

      if (mounted) {
        context.loaderOverlay.hide();
        context.go(HomePage.name);
      }

    } on FirebaseAuthException catch (e) {
      context.loaderOverlay.hide();

      // ...
      
    } catch (e) {
      // ...
    }
    
  }
}

メールユーザー登録を追加

このセクションでは、Todo アプリにメールアドレスのユーザー登録を追加します。

ステップ1: ユーザー登録コードを追加

/lib/features/auth/email_signup_page.dart を開き、32行目の signUp() にユーザー登録のコードを追加します。

signUp() async {
  if (...) {
    final credential = await FirebaseAuth.instance.createUserWithEmailAndPassword(
      email: _email,
      password: _password,
    );
  }
}

前のセクションと同様に、ユーザー登録に失敗したときの例外処理を追加します。

signUp() async {
  if (...) {
    try {
      final credential = await FirebaseAuth.instance.createUserWithEmailAndPassword(
        email: _email,
        password: _password,
      );  

    } on FirebaseAuthException catch (e) {
      if (e.code == 'weak-password') {
        messenger.showSnackBar(const SnackBar(content: Text('より複雑なパスワードを使用してください')));
      } else if (e.code == 'email-already-in-use') {
        messenger.showSnackBar(const SnackBar(content: Text('メールアドレスが既に使用されています')));
      }

    } catch (e) {
      if (kDebugMode) {
        print(e);
      }
      
    }
  }
}

ステップ2: LoaderOverlay を表示・非表示する

signIn() async {
  if (...) {
    // LoaderOverlayを追加
    context.loaderOverlay.show();
    final messenger = ScaffoldMessenger.of(context);

    try {
      // ...

      if (mounted) {
        context.loaderOverlay.hide();
        context.go(HomePage.name);
      }

    } on FirebaseAuthException catch (e) {
      context.loaderOverlay.hide();

      // ...
      
    } catch (e) {
      // ...
    }
    
  }
}

Google認証の実装

前提条件

ステップ1: (Androidのみ) SHA KEYを入手

ターミナルまたはコマンドプロンプトを起動し、Todo アプリのルートディレクトリに移動します。以下のコマンドを実行します。

cd .\android\
./gradlew signingReport

signingReport

> Task :google_sign_in_android:signingReport で出力される SHA-1 と SHA-256 をメモしておきます。

sha

ステップ2: Google認証を有効化

ユーザーデータを追加

このセクションでは、ユーザーのデータを Firestore に保存します。
/lib/api/firestore_api.dart を開き、以下のコードを追加します。
このコードでは、データベースの users コレクションにユーザーの UID (User ID) を ID としたデータをセットしています。

final _db = FirebaseFirestore.instance;
final _users = _db.collection('users');

class FirestoreAPI {
  Future<void> addUser(User user) async {
    final userData = DBUser.map();
    await _users.doc(user.uid).set(userData);
    existsDBUser = true;
  }
}

次に、Firebase Auth の実装で使用した /lib/features/auth/email_signup_page.dart を開き、以下のコードを追加します。

signUp() async {
  if (...) {
    // ...

    try {
      final credential = await FirebaseAuth.instance.createUserWithEmailAndPassword(
        email: _email,
        password: _password,
      );

      final user = credential.user;

      if (user != null) {
        FirestoreAPI.instance.addUser(user);
      }

      // ...
    } on FirebaseAuthException catch (e) {
      // ...
    }
  }
}

これにより、新しくユーザー登録されたときに Firestore にもデータが追加されるようになりました。
次のセクションでは、Todo のタスクを追加する方法を学びます。

タスクデータを追加

このセクションでは、タスクのCRUD(Create, Read, Update, Delete) の方法を学びます。

ステップ1: タスクの読み取り

以下のプロバイダを作成し、全てのタスクの読み取りを実装します。

final allTaskProvider = StreamProvider.autoDispose<List<Task>>((ref) {
  return ref.watch(userProvider).when(
      data: (user) {
        if (user == null) {
          return const Stream.empty();
        } else {
          final uid = user.uid;
          final ref = _db.collection('users').doc(uid).collection('tasks');

          final snapshots = ref.snapshots();

          return snapshots.map((snapshot) {
            final tasks = snapshot.docs
                .map((doc) {
                  final data = doc.data();

                  if (data.containsKey('updatedAt') && data['updatedAt'] == null) {
                    data['updatedAt'] = Timestamp.fromDate(DateTime.now());
                  }

                  return Task.fromJson(data);
            }).toList();

            return tasks;
          });
        }
      },
      error: (err, stack) => const Stream.empty(),
      loading: () => const Stream.empty());
});

次に、特定のタスクを取得するプロバイダを作成します。

final taskProvider = StreamProvider.autoDispose.family<Task, String>((ref, taskId) {
  final user = _auth.currentUser;
  
  if (user == null) {
    return const Stream.empty();
  } else {
    final uid = user.uid;
    final ref = _db.collection('users').doc(uid).collection('tasks').doc(taskId);
    final data = ref.snapshots().map((event) {
      final data = event.data();

      if (data == null) {
        return null;
      }
      
      if (data.containsKey('updatedAt') && data['updatedAt'] == null) {
        data['updatedAt'] = Timestamp.fromDate(DateTime.now());
      }
      
      return Task.fromJson(data);
    }).where((task) => task != null).cast<Task>();

    return data;
  }
});

ステップ2: タスクの書き込み

Storage