【メンバー管理画面】チームの参加メンバーを一覧表示する
このページでやること
このページでは、チームに参加しているメンバーを一覧表示します。
メンバー情報は、Firestoreのここに保存されています。
teams/{teamId}/members/{uid}
このデータを取得して、画面に表示します。
チーム詳細画面
↓
メンバー管理画面を開く
↓
参加メンバーを一覧表示
今日のゴール
チームのメンバーを、次のように表示できるようにします。
山田 太郎
yamada@example.com
オーナー
佐藤 花子
sato@example.com
メンバー
このページでは、追加・削除・権限変更はまだ行いません。
まずは、参加メンバーを表示するだけです。
npmや環境変数は必要?
このページでは、npmは使いません。
環境変数も設定しません。
すでに使っているFirestoreだけで進めます。
必要なimportはこちらです。
import 'package:cloud_firestore/cloud_firestore.dart';
Step 1:main.dartを開く
次のファイルを開きます。
lib/main.dart
ターミナルから開く場合はこちらです。
code lib/main.dart
Step 2:メンバーの保存場所を確認する
チームメンバーは、Firestoreのここに保存されています。
teams/{teamId}/members/{uid}
例です。
teams/team001/members/user001
uid: user001
email: yamada@example.com
displayName: 山田 太郎
role: owner
joinedAt: 参加日時
この中で、画面に出すのは主にこの3つです。
displayName
email
role
Step 3:TaskListPageにメンバー画面へのボタンを追加する
まず、タスク一覧画面の右上から、メンバー管理画面を開けるようにします。
TaskListPage の AppBar を探します。
appBar: AppBar(
title: Text(widget.teamName),
),
これを、次のように変更します。
appBar: AppBar(
title: Text(widget.teamName),
actions: [
IconButton(
tooltip: 'メンバー',
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => MemberListPage(
teamId: widget.teamId,
teamName: widget.teamName,
),
),
);
},
icon: const Icon(Icons.group),
),
],
),
これで、右上の人型アイコンからメンバー一覧画面を開けます。
Step 4:MemberListPageを作る
TaskListPage の下あたりに、次のコードを追加します。
class MemberListPage extends StatelessWidget {
const MemberListPage({
super.key,
required this.teamId,
required this.teamName,
});
final String teamId;
final String teamName;
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.bg,
appBar: AppBar(
title: Text('$teamNameのメンバー'),
),
body: StreamBuilder<QuerySnapshot<Map<String, dynamic>>>(
stream: FirebaseFirestore.instance
.collection('teams')
.doc(teamId)
.collection('members')
.snapshots(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const LoadingPage(message: 'メンバーを読み込んでいます...');
}
if (snapshot.hasError) {
return Center(
child: Padding(
padding: const EdgeInsets.all(20),
child: ErrorBox(
message: 'メンバー一覧の取得に失敗しました。',
),
),
);
}
final members = snapshot.data?.docs ?? [];
if (members.isEmpty) {
return const Center(
child: Text(
'まだメンバーがいません。',
style: TextStyle(
color: AppColors.subText,
fontWeight: FontWeight.w700,
),
),
);
}
return ListView.separated(
padding: const EdgeInsets.all(16),
itemCount: members.length,
separatorBuilder: (_, __) => const SizedBox(height: 10),
itemBuilder: (context, index) {
final doc = members[index];
final data = doc.data();
final displayName =
(data['displayName'] ?? '名前未設定').toString();
final email = (data['email'] ?? '').toString();
final roleText = (data['role'] ?? 'member').toString();
final role = teamRoleFromString(roleText);
return MemberCard(
displayName: displayName,
email: email,
role: role,
);
},
);
},
),
);
}
}
これで、Firestoreからメンバー一覧を取得できます。
Step 5:Firestoreからmembersを取得する部分を見る
今回の中心はここです。
FirebaseFirestore.instance
.collection('teams')
.doc(teamId)
.collection('members')
.snapshots()
意味はこうです。
teamsを見る
↓
選んだteamIdを見る
↓
その中のmembersを見る
↓
変更があれば自動で画面更新
取得場所はここです。
teams/{teamId}/members
Step 6:MemberCardを作る
次に、メンバー1人分を表示する MemberCard を作ります。
MemberListPage の下に、次のコードを追加します。
class MemberCard extends StatelessWidget {
const MemberCard({
super.key,
required this.displayName,
required this.email,
required this.role,
});
final String displayName;
final String email;
final TeamRole role;
@override
Widget build(BuildContext context) {
return AppCard(
child: Row(
children: [
CircleAvatar(
backgroundColor: AppColors.lineGreen,
child: Text(
displayName.isNotEmpty ? displayName.substring(0, 1) : '?',
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.w900,
),
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
displayName,
style: const TextStyle(
color: AppColors.text,
fontSize: 16,
fontWeight: FontWeight.w900,
),
),
if (email.isNotEmpty) ...[
const SizedBox(height: 4),
Text(
email,
style: const TextStyle(
color: AppColors.subText,
fontSize: 13,
fontWeight: FontWeight.w600,
),
),
],
],
),
),
_Label(text: teamRoleLabel(role)),
],
),
);
}
}
MemberCard は、メンバー1人分の表示部品です。
Step 7:TeamRoleを確認する
前のページで作った TeamRole があるか確認します。
enum TeamRole {
owner,
admin,
member,
viewer,
}
Firestoreの role は文字列です。
owner
admin
member
viewer
Dart側では、これを TeamRole に変換して扱います。
Step 8:teamRoleFromStringを確認する
次の関数があるか確認します。
TeamRole teamRoleFromString(String? value) {
switch (value) {
case 'owner':
return TeamRole.owner;
case 'admin':
return TeamRole.admin;
case 'viewer':
return TeamRole.viewer;
case 'member':
default:
return TeamRole.member;
}
}
これは、Firestoreの文字列をDartの権限に変換する関数です。
owner → TeamRole.owner
admin → TeamRole.admin
member → TeamRole.member
viewer → TeamRole.viewer
Step 9:teamRoleLabelを確認する
画面では、日本語で表示します。
次の関数があるか確認します。
String teamRoleLabel(TeamRole role) {
switch (role) {
case TeamRole.owner:
return 'オーナー';
case TeamRole.admin:
return '管理者';
case TeamRole.member:
return 'メンバー';
case TeamRole.viewer:
return '閲覧者';
}
}
これで、画面表示はこうなります。
TeamRole.owner → オーナー
TeamRole.admin → 管理者
TeamRole.member → メンバー
TeamRole.viewer → 閲覧者
Step 10:右上のアイコンから開く流れを確認する
今回の画面遷移は、こうです。
TaskListPage
↓
右上のメンバーアイコンを押す
↓
MemberListPageを開く
↓
teamIdを使ってmembersを取得
teamId を渡しているので、そのチームだけのメンバーを表示できます。
MemberListPage(
teamId: widget.teamId,
teamName: widget.teamName,
)
Step 11:保存する
main.dart を保存します。
Macの場合:
command + S
Windowsの場合:
Ctrl + S
Step 12:実行する
ターミナルで実行します。
flutter run
すでに起動している場合は、ターミナルで r を押します。
r
うまく反映されない場合は、R を押します。
R
Step 13:画面を確認する
アプリを開きます。
ログイン
↓
トーク一覧
↓
チームをタップ
↓
右上のメンバーアイコンを押す
メンバー一覧画面が開きます。
チーム作成者が表示されていれば成功です。
自分の名前
自分のメールアドレス
オーナー
Step 14:Firestoreで確認する
Firebase Consoleを開きます。
https://console.firebase.google.com/
次の場所を確認します。
Firestore Database
↓
teams
↓
作成したteamId
↓
members
ここに、自分の uid のドキュメントがあるはずです。
中身は、このような形です。
uid: 自分のuid
email: 自分のメールアドレス
displayName: 自分の名前
role: owner
joinedAt: 参加日時
Step 15:テスト用メンバーを手動で追加する
まだメンバー追加機能がないので、テストしたい場合はFirebase Consoleで手動追加します。
members の中に新しいドキュメントを作ります。
ドキュメントIDは仮でOKです。
test-user-001
フィールドは次のように入れます。
uid: test-user-001
email: test@example.com
displayName: テスト太郎
role: member
joinedAt: 現在時刻
アプリに戻って、テスト太郎が表示されればOKです。
よくあるエラーと直し方
| エラー | 原因 | 直し方 |
|---|---|---|
MemberListPage isn't defined | 画面を作っていない | MemberListPage を追加 |
MemberCard isn't defined | カードを作っていない | MemberCard を追加 |
teamRoleFromString isn't defined | 変換関数がない | 前ページの関数を追加 |
teamRoleLabel isn't defined | 日本語表示関数がない | teamRoleLabel() を追加 |
_Label isn't defined | ラベル部品がない | _Label を追加 |
| メンバーが表示されない | membersにデータがない | Firestoreのmembersを確認 |
permission-denied | Firestore Rulesで拒否 | 開発用Rulesを確認 |
permission-denied が出たとき
開発中だけ、Firestore Rulesを次のようにして確認できます。
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if true;
}
}
}
これは学習用です。
本番公開では使わないでください。
本番では、チームメンバーだけがメンバー一覧を見られるようにします。
文字の1文字目でエラーが出る場合
この部分でエラーが出ることがあります。
displayName.substring(0, 1)
displayName が空の場合は ? を出すようにしています。
displayName.isNotEmpty ? displayName.substring(0, 1) : '?'
日本語や絵文字をより安全に扱うなら、次のようにしてもOKです。
displayName.isNotEmpty ? displayName.characters.first : '?'
ただし、characters でエラーが出る場合は、substring(0, 1) のままで進めてください。
最短作業まとめ
読むのが大変な人は、ここだけ見てください。
1. AppBarにメンバーボタンを追加
actions: [
IconButton(
tooltip: 'メンバー',
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => MemberListPage(
teamId: widget.teamId,
teamName: widget.teamName,
),
),
);
},
icon: const Icon(Icons.group),
),
],
2. membersを取得する
stream: FirebaseFirestore.instance
.collection('teams')
.doc(teamId)
.collection('members')
.snapshots(),
3. メンバーを表示する
return MemberCard(
displayName: displayName,
email: email,
role: role,
);
4. 保存して実行
flutter run
チェックリスト
□ main.dartを開いた
□ TaskListPageのAppBarにメンバーアイコンを追加した
□ MemberListPageを作った
□ teamIdを受け取れるようにした
□ teamNameを受け取れるようにした
□ teams/{teamId}/members を取得した
□ displayNameを取り出した
□ emailを取り出した
□ roleを取り出した
□ teamRoleFromStringでroleを変換した
□ MemberCardを作った
□ roleを日本語で表示した
□ 保存した
□ flutter runで確認した
□ メンバー一覧が表示された
ミニ確認問題
Q1. メンバー一覧はFirestoreのどこから取得しますか?
回答
次の場所から取得します。
teams/{teamId}/members
Q2. メンバーの権限はどの項目に保存されていますか?
回答
role に保存されています。
Q3. Firestoreの role: owner は画面では何と表示しますか?
回答
オーナー と表示します。
Q4. このページでnpmや環境変数は必要ですか?
回答
必要ありません。
Firestoreから members を取得して表示するだけです。
このページのまとめ
- メンバー情報は
teams/{teamId}/members/{uid}に保存する。 - メンバー一覧は
teams/{teamId}/membersから取得する。 StreamBuilderとsnapshots()でリアルタイムに表示する。- 表示する主な項目は
displayName、email、role。 roleはTeamRoleに変換して扱う。- 画面では
オーナー、管理者、メンバー、閲覧者と表示する。 - このページではnpmや環境変数は不要。
次のページでやること
次のページでは、メンバーをメールアドレスで追加する画面を作ります。
