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

【プロフィール保存】users/{uid} にdisplayName・email・departmentを保存する

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

このページでやること

このページでは、新規登録したユーザーのプロフィールをFirestoreに保存します。

Firestoreとは、Firebaseのデータベースです。

データベースとは、アプリの情報を保存する場所です。

前のページでは、Firebase Authenticationにアカウントを作成しました。

今回は、そのユーザーの名前・メールアドレス・所属を、Firestoreの users/{uid} に保存します。

Firebase Authentication
↓
ログイン用アカウントを作る

Cloud Firestore
↓
アプリ内で使うプロフィールを保存する

今日のゴール

新規登録ボタンを押したときに、次の流れで動くようにします。

名前を入力する
↓
所属を入力する
↓
メールアドレスを入力する
↓
パスワードを入力する
↓
Firebase Authenticationにユーザーを作成する
↓
uidを取得する
↓
Firestoreの users/{uid} にプロフィールを保存する

保存するデータは、次の内容です。

保存する項目一言説明
displayName画面に表示する名前
emailログインに使うメールアドレス
department所属・部署・チーム名
photoUrlプロフィール画像URL。今回は空文字
defaultRole初期の権限。今回は member
createdAt作成日時
updatedAt更新日時

uid とは、Firebase Authenticationがユーザーごとに発行するIDです。


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

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

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

このページで必要なのは、すでに追加したこの2つです。

import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';

firebase_auth はログイン・新規登録に使います。

cloud_firestore はFirestoreにデータを保存するために使います。


Step 1:Firebase ConsoleでFirestoreを有効にする

まだFirestoreを作っていない場合は、Firebase Consoleで有効にします。

Firebase Consoleを開きます。

https://console.firebase.google.com/

次の流れで進めます。

Firebase Console
↓
Firestore Database
↓
データベースを作成
↓
テストモードで開始
↓
ロケーションを選択
↓
有効化

テストモードとは、開発中に読み書きしやすい設定です。

本番公開では、安全なSecurity Rulesに変更します。

本番公開とは、実際のユーザーにアプリを使ってもらう状態のことです。


Step 2:main.dartを開く

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

lib/main.dart

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

code lib/main.dart

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


Step 3:Firestoreのimportを確認する

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

import 'package:cloud_firestore/cloud_firestore.dart';

この1行がないと、FirebaseFirestore が使えません。

FirebaseFirestore とは、FlutterからFirestoreを操作するための入口です。

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

完成コードの上部は、だいたい次のようになります。

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';

import 'firebase_options.dart';

Step 4:今のregister()を探す

RegisterPage の中にある register() を探します。

前のページでは、次のような処理を書きました。

final credential =
    await FirebaseAuth.instance.createUserWithEmailAndPassword(
  email: email,
  password: password,
);

final user = credential.user;

if (user == null) {
  setState(() {
    errorText = 'ユーザー作成に失敗しました。';
  });
  return;
}

if (mounted) {
  Navigator.of(context).pop();
}

このままだと、Authenticationにユーザーは作れますが、Firestoreにプロフィールは保存されません。

今回は、この間にFirestore保存処理を追加します。


Step 5:register()を差し替える

RegisterPageregister() を、次のコードに置き換えてください。

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

  final displayName = displayNameController.text.trim();
  final department = departmentController.text.trim();
  final email = emailController.text.trim();
  final password = passwordController.text;

  if (displayName.isEmpty ||
      department.isEmpty ||
      email.isEmpty ||
      password.isEmpty) {
    setState(() {
      isLoading = false;
      errorText = 'すべての項目を入力してください。';
    });
    return;
  }

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

    final user = credential.user;

    if (user == null) {
      setState(() {
        errorText = 'ユーザー作成に失敗しました。';
      });
      return;
    }

    await FirebaseFirestore.instance.collection('users').doc(user.uid).set({
      'displayName': displayName,
      'email': email,
      'department': department,
      'photoUrl': '',
      'defaultRole': 'member',
      'createdAt': FieldValue.serverTimestamp(),
      'updatedAt': FieldValue.serverTimestamp(),
    });

    if (mounted) {
      Navigator.of(context).pop();
    }
  } on FirebaseAuthException catch (e) {
    setState(() {
      errorText = e.message ?? '新規登録に失敗しました。';
    });
  } on FirebaseException catch (e) {
    setState(() {
      errorText = e.message ?? 'プロフィール保存に失敗しました。';
    });
  } catch (_) {
    setState(() {
      errorText = '新規登録に失敗しました。';
    });
  } finally {
    if (mounted) {
      setState(() {
        isLoading = false;
      });
    }
  }
}

これで、Authenticationにユーザーを作ったあと、Firestoreにもプロフィールを保存できます。


Step 6:Firestore保存部分だけ見る

今回追加した中心部分はここです。

await FirebaseFirestore.instance.collection('users').doc(user.uid).set({
  'displayName': displayName,
  'email': email,
  'department': department,
  'photoUrl': '',
  'defaultRole': 'member',
  'createdAt': FieldValue.serverTimestamp(),
  'updatedAt': FieldValue.serverTimestamp(),
});

短く言うと、こういう意味です。

usersコレクションを選ぶ
↓
user.uidをドキュメントIDにする
↓
名前・メール・所属を保存する

collection は、Firestoreのコレクションを選ぶ命令です。

コレクションとは、同じ種類のデータをまとめる箱です。

doc は、ドキュメントを指定する命令です。

ドキュメントとは、1件分のデータです。

set は、データを保存する命令です。


Step 7:users/{uid} の意味

今回の保存先は、次の形です。

users/{uid}

たとえば、Firebase Authenticationで作られた uidabc123 だった場合、保存先はこうなります。

users/abc123

この中に、プロフィールを保存します。

users/abc123
  displayName: 山田太郎
  email: test@example.com
  department: 開発チーム
  photoUrl:
  defaultRole: member
  createdAt: 作成日時
  updatedAt: 更新日時

uid をドキュメントIDにすると、AuthenticationのユーザーとFirestoreのプロフィールを結びつけやすくなります。


Step 8:FieldValue.serverTimestamp()とは何か

この部分を見ます。

'createdAt': FieldValue.serverTimestamp(),
'updatedAt': FieldValue.serverTimestamp(),

FieldValue.serverTimestamp() は、Firestore側の現在時刻を保存する命令です。

現在時刻とは、データを保存した日時のことです。

スマホやパソコンの時計ではなく、Firestore側の時刻を使います。

そのため、ユーザーの端末の時計が少しズレていても、保存時刻をそろえやすくなります。

createdAt
↓
最初に作成した日時

updatedAt
↓
最後に更新した日時

今回は新規作成なので、両方に同じタイミングの時刻を入れます。


Step 9:FirebaseExceptionを追加した理由

今回のコードには、次の部分があります。

} on FirebaseException catch (e) {
  setState(() {
    errorText = e.message ?? 'プロフィール保存に失敗しました。';
  });
}

FirebaseException は、FirestoreなどFirebase全体で起きるエラーです。

Authenticationのエラーは FirebaseAuthException

Firestoreのエラーは FirebaseException として受け取ることが多いです。

たとえば、FirestoreのRulesで拒否された場合などにエラーになります。

Rulesとは、誰がデータを読めるか・書けるかを決める設定です。


Step 10:保存する

main.dart を保存します。

Macの場合:

command + S

Windowsの場合:

Ctrl + S

Step 11:実行する

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

flutter run

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

r

画面構成やFirebase処理を変えたあとに動きがおかしい場合は、R でホットリスタートしてください。

R

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

R はホットリスタートです。

ホットリスタートとは、アプリの状態をリセットして起動し直す機能です。


Step 12:新規登録を試す

ログイン画面から、次を押します。

新規登録はこちら

新規登録画面で、次のように入力します。

名前:山田太郎
所属:開発チーム
メールアドレス:test2@example.com
パスワード:password123

前のページで test@example.com を使った場合は、別のメールアドレスを使ってください。

同じメールアドレスを使うと、すでに登録済みのエラーになります。

入力したら、新規登録ボタンを押します。

新規登録

Step 13:Authenticationで確認する

Firebase Consoleを開きます。

https://console.firebase.google.com/

次の場所を見ます。

Authentication
↓
Users

登録したメールアドレスがあればOKです。

test2@example.com

Step 14:Firestoreで確認する

次に、Firestoreを開きます。

Firestore Database
↓
データ
↓
users

users コレクションの中に、ユーザーの uid がドキュメントIDとして作られていれば成功です。

中身に次の項目があるか確認します。

displayName
email
department
photoUrl
defaultRole
createdAt
updatedAt

ここまで確認できたら、このページのゴール達成です。


うまくいった状態

成功すると、Firebase側は次のようになります。

Authentication
└ test2@example.com が作成される

Firestore
└ users
   └ uid
      ├ displayName
      ├ email
      ├ department
      ├ photoUrl
      ├ defaultRole
      ├ createdAt
      └ updatedAt

このように、ログイン用アカウントとプロフィール情報を分けて保存します。


よくあるエラーと直し方

エラー原因直し方
FirebaseFirestore isn't definedimportがないcloud_firestore.dart をimportする
FieldValue isn't definedimportがないcloud_firestore.dart をimportする
permission-deniedFirestore Rulesで拒否されている開発用Rulesを確認する
email-already-in-useメールが登録済み別のメールを使う
operation-not-allowedEmail/Passwordが無効Authenticationで有効にする
Firestoreにusersが出ない保存処理が動いていないset() の位置を確認する
画面が変わらない保存していないcommand + S

permission-denied** が出たとき**

このエラーは、FirestoreのRulesで書き込みが拒否されているときに出ます。

学習中だけ、Firestore Rulesを次のようにして動作確認できます。

rules_version = '2';

service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if true;
    }
  }
}

これは開発用です。

本番公開では使わないでください。

本番では、ログインユーザーだけが必要なデータを読み書きできるようにします。


email-already-in-use** が出たとき**

これは、そのメールアドレスがすでに使われているという意味です。

学習中は、別のメールアドレスで試してください。

例:

test3@example.com
test4@example.com
test5@example.com

または、Firebase ConsoleのAuthenticationからテストユーザーを削除して、もう一度試します。


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

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

import 'package:cloud_firestore/cloud_firestore.dart';

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

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

flutter run

最短作業まとめ

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

1. importを確認

import 'package:cloud_firestore/cloud_firestore.dart';

2. register()内でユーザー作成後にこれを追加

await FirebaseFirestore.instance.collection('users').doc(user.uid).set({
  'displayName': displayName,
  'email': email,
  'department': department,
  'photoUrl': '',
  'defaultRole': 'member',
  'createdAt': FieldValue.serverTimestamp(),
  'updatedAt': FieldValue.serverTimestamp(),
});

3. 保存して実行

flutter run

4. Firebase Consoleで確認

Authentication
↓
Users

Firestore Database
↓
users

チェックリスト

□ Firestore Databaseを有効にした
□ main.dartを開いた
□ cloud_firestoreをimportした
□ register()を差し替えた
□ createUserWithEmailAndPasswordのあとにset()を書いた
□ users/{uid} に保存する形にした
□ displayNameを保存した
□ emailを保存した
□ departmentを保存した
□ createdAtを保存した
□ updatedAtを保存した
□ 保存した
□ flutter runで実行した
□ Authenticationでユーザーを確認した
□ Firestoreのusersでプロフィールを確認した

ミニ確認問題

Q1. users/{uid} には何を保存しますか?

回答

ユーザーのプロフィールを保存します。

今回保存するのは、displayNameemaildepartmentphotoUrldefaultRolecreatedAtupdatedAt です。


Q2. なぜドキュメントIDに uid を使いますか?

回答

Firebase AuthenticationのユーザーとFirestoreのプロフィールを結びつけやすくするためです。

uid はユーザーごとに一意なので、プロフィールの保存先として使いやすいです。


Q3. FieldValue.serverTimestamp() は何をしますか?

回答

Firestore側の現在時刻を保存します。

作成日時や更新日時を保存するときに使います。


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

回答

必要ありません。

このページでは、Firestoreにプロフィールを保存するコードを追加するだけです。


このページのまとめ

  • Firebase Authenticationはログイン用アカウントを管理する。
  • Firestoreはアプリ内で使うプロフィールを保存する。
  • 新規登録後に、users/{uid} にプロフィールを保存する。
  • uid はFirebase Authenticationがユーザーごとに発行するID。
  • collection('users').doc(user.uid).set(...) でプロフィールを保存する。
  • displayNameemaildepartment を保存する。
  • createdAtupdatedAt には FieldValue.serverTimestamp() を使う。
  • permission-denied が出たらFirestore Rulesを確認する。
  • このページではnpmや環境変数は不要。

次のページでやること

次のページでは、ログアウト処理を作ります。

FirebaseAuth.instance.signOut() を使って、ログイン状態を解除し、ログイン画面へ戻る流れを確認します。

教材トップへ戻る