【権限チップ】ChoiceChipでadmin・member・viewerを選択できるようにする
このページでやること
このページでは、メンバー追加画面で権限を選べるようにします。
前のページでは、追加したメンバーは自動で member になっていました。
今回は、追加時に次の3つから選べるようにします。
admin
member
viewer
画面では、日本語で表示します。
管理者
メンバー
閲覧者
今日のゴール
メンバー追加ボトムシートに、権限を選ぶチップを表示します。
メンバーを追加
メールアドレス
[ sato@example.com ]
権限
[ 管理者 ] [ メンバー ] [ 閲覧者 ]
[追加する]
最初は member を選択状態にします。
初期選択:メンバー
npmや環境変数は必要?
このページでは、npmは使いません。
環境変数も設定しません。
Flutterの標準Widgetである ChoiceChip を使います。
main.dartを開く
↓
selectedMemberRoleを作る
↓
ChoiceChipを表示する
↓
保存時のroleに使う
Step 1:main.dartを開く
次のファイルを開きます。
lib/main.dart
ターミナルから開く場合はこちらです。
code lib/main.dart
Step 2:MemberListPageを探す
main.dart の中で、次を検索します。
class MemberListPage
VS Codeなら、次で検索できます。
command + F
今回は、_MemberListPageState の中を編集します。
Step 3:選択中の権限を保存する変数を作る
_MemberListPageState の中に、次を追加します。
String selectedMemberRole = 'member';
完成イメージです。
class _MemberListPageState extends State<MemberListPage> {
final emailController = TextEditingController();
String selectedMemberRole = 'member';
bool isAdding = false;
String? errorText;
@override
void dispose() {
emailController.dispose();
super.dispose();
}
}
selectedMemberRole は、現在選ばれている権限を覚えるための変数です。
Step 4:ボトムシートを開くときに初期化する
showAddMemberSheet() の最初を確認します。
Future<void> showAddMemberSheet() async {
emailController.clear();
errorText = null;
ここに、権限の初期値を追加します。
Future<void> showAddMemberSheet() async {
emailController.clear();
selectedMemberRole = 'member';
errorText = null;
これで、ボトムシートを開くたびに「メンバー」が選ばれた状態になります。
Step 5:ChoiceChipとは?
ChoiceChip は、複数の選択肢から1つを選ぶためのWidgetです。
今回は、3つのチップを出します。
管理者
メンバー
閲覧者
内部では、次の値として保存します。
admin
member
viewer
画面表示は日本語
Firestore保存は英語
Step 6:権限チップUIを追加する
showAddMemberSheet() の中で、メールアドレス入力欄の下に追加します。
この部分を探します。
AppTextField(
controller: emailController,
label: 'メールアドレス',
keyboardType: TextInputType.emailAddress,
),
その下に、次を追加します。
const SizedBox(height: 16),
const Text(
'権限',
style: TextStyle(
color: AppColors.text,
fontWeight: FontWeight.w900,
),
),
const SizedBox(height: 8),
Wrap(
spacing: 8,
runSpacing: 8,
children: [
ChoiceChip(
label: const Text('管理者'),
selected: selectedMemberRole == 'admin',
onSelected: (_) {
setSheetState(() {
selectedMemberRole = 'admin';
});
},
),
ChoiceChip(
label: const Text('メンバー'),
selected: selectedMemberRole == 'member',
onSelected: (_) {
setSheetState(() {
selectedMemberRole = 'member';
});
},
),
ChoiceChip(
label: const Text('閲覧者'),
selected: selectedMemberRole == 'viewer',
onSelected: (_) {
setSheetState(() {
selectedMemberRole = 'viewer';
});
},
),
],
),
これで、ボトムシート内で権限を選べます。
Step 7:ChoiceChipの意味を見る
1つ分を見ると、こうです。
ChoiceChip(
label: const Text('メンバー'),
selected: selectedMemberRole == 'member',
onSelected: (_) {
setSheetState(() {
selectedMemberRole = 'member';
});
},
),
意味はこうです。
表示名:メンバー
保存値:member
今memberなら選択中にする
押されたらmemberに変更する
大事なのは、この2つです。
selected: selectedMemberRole == 'member',
selectedMemberRole = 'member';
Step 8:Wrapを使う理由
チップは Wrap の中に置きます。
Wrap(
spacing: 8,
runSpacing: 8,
children: [
...
],
)
Wrap を使うと、画面が狭いときに自動で折り返されます。
横幅が広い
↓
管理者 メンバー 閲覧者
横幅が狭い
↓
管理者 メンバー
閲覧者
スマホ向けに安全です。
Step 9:保存処理で選択した権限を使う
前のページでは、メンバー追加時に固定で member を保存していました。
'role': 'member',
これを変更します。
'role': selectedMemberRole,
addMemberByEmail() の中を探して、次のようにします。
await memberRef.set({
'uid': uid,
'email': userEmail,
'displayName': displayName,
'role': selectedMemberRole,
'joinedAt': FieldValue.serverTimestamp(),
});
これで、選んだ権限がFirestoreに保存されます。
Step 10:完成形のshowAddMemberSheet
showAddMemberSheet() は、次の形になっていればOKです。
Future<void> showAddMemberSheet() async {
emailController.clear();
selectedMemberRole = 'member';
errorText = null;
await showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
backgroundColor: Colors.white,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(20),
),
),
builder: (context) {
return StatefulBuilder(
builder: (context, setSheetState) {
return Padding(
padding: EdgeInsets.only(
left: 20,
right: 20,
top: 20,
bottom: MediaQuery.of(context).viewInsets.bottom + 20,
),
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Container(
width: 44,
height: 5,
margin: const EdgeInsets.only(bottom: 18),
decoration: BoxDecoration(
color: AppColors.border,
borderRadius: BorderRadius.circular(999),
),
),
const Text(
'メンバーを追加',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w900,
color: AppColors.text,
),
),
const SizedBox(height: 8),
const Text(
'登録済みユーザーのメールアドレスを入力してください。',
style: TextStyle(
color: AppColors.subText,
height: 1.5,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 16),
AppTextField(
controller: emailController,
label: 'メールアドレス',
keyboardType: TextInputType.emailAddress,
),
const SizedBox(height: 16),
const Text(
'権限',
style: TextStyle(
color: AppColors.text,
fontWeight: FontWeight.w900,
),
),
const SizedBox(height: 8),
Wrap(
spacing: 8,
runSpacing: 8,
children: [
ChoiceChip(
label: const Text('管理者'),
selected: selectedMemberRole == 'admin',
onSelected: (_) {
setSheetState(() {
selectedMemberRole = 'admin';
});
},
),
ChoiceChip(
label: const Text('メンバー'),
selected: selectedMemberRole == 'member',
onSelected: (_) {
setSheetState(() {
selectedMemberRole = 'member';
});
},
),
ChoiceChip(
label: const Text('閲覧者'),
selected: selectedMemberRole == 'viewer',
onSelected: (_) {
setSheetState(() {
selectedMemberRole = 'viewer';
});
},
),
],
),
if (errorText != null) ...[
const SizedBox(height: 12),
ErrorBox(message: errorText!),
],
const SizedBox(height: 16),
FilledButton(
onPressed: isAdding
? null
: () async {
await addMemberByEmail(setSheetState);
},
child: Text(isAdding ? '追加中...' : '追加する'),
),
],
),
),
);
},
);
},
);
}
Step 11:完成形の保存処理
addMemberByEmail() の保存部分は、次のようにします。
await memberRef.set({
'uid': uid,
'email': userEmail,
'displayName': displayName,
'role': selectedMemberRole,
'joinedAt': FieldValue.serverTimestamp(),
});
固定の member ではなく、選択した値を保存します。
管理者を選択 → role: admin
メンバーを選択 → role: member
閲覧者を選択 → role: viewer
Step 12:保存する
main.dart を保存します。
Macの場合:
command + S
Windowsの場合:
Ctrl + S
Step 13:実行する
ターミナルで実行します。
flutter run
すでに起動している場合は、ターミナルで r を押します。
r
うまく反映されない場合は、R を押します。
R
Step 14:画面を確認する
アプリを開きます。
ログイン
↓
トーク一覧
↓
チームをタップ
↓
右上のメンバーアイコン
↓
右下の+
メンバー追加ボトムシートに、次のチップが表示されればOKです。
管理者 メンバー 閲覧者
最初は「メンバー」が選択されていれば成功です。
Step 15:権限を選んで追加してみる
登録済みユーザーのメールアドレスを入力します。
sato@example.com
権限を選びます。
管理者
「追加する」を押します。
メンバー一覧に表示され、権限が「管理者」になっていれば成功です。
Step 16:Firestoreで確認する
Firebase Consoleを開きます。
https://console.firebase.google.com/
次を確認します。
Firestore Database
↓
teams
↓
作成したteamId
↓
members
↓
追加したuid
role が選んだ値になっていればOKです。
管理者を選んだ場合
↓
role: admin
閲覧者を選んだ場合
↓
role: viewer
よくあるエラーと直し方
| エラー | 原因 | 直し方 |
|---|---|---|
ChoiceChip isn't defined | Materialのimport不足 | import 'package:flutter/material.dart'; を確認 |
selectedMemberRole isn't defined | 変数がない | String selectedMemberRole = 'member'; を追加 |
| チップを押しても変わらない | setSheetState を使っていない | setSheetState(() { ... }) を使う |
| 追加してもroleがmember固定 | 保存時が 'role': 'member' のまま | 'role': selectedMemberRole に変更 |
| ボトムシートを開くたび前回の権限が残る | 初期化していない | selectedMemberRole = 'member'; を追加 |
| role表示が不明になる | role文字列が違う | admin/member/viewer のどれかにする |
ownerを選択肢に入れない理由
今回、追加時に選べるのはこの3つだけです。
admin
member
viewer
owner は入れません。
理由は、owner はチームの最上位権限だからです。
owner → チーム作成者
admin → 管理者として追加できる
member → 通常メンバー
viewer → 閲覧者
owner を簡単に増やせると危険なので、追加画面では選ばせません。
最短作業まとめ
読むのが大変な人は、ここだけ見てください。
1. 選択中の権限を作る
String selectedMemberRole = 'member';
2. ボトムシートを開くときに初期化
selectedMemberRole = 'member';
3. ChoiceChipを追加
Wrap(
spacing: 8,
runSpacing: 8,
children: [
ChoiceChip(
label: const Text('管理者'),
selected: selectedMemberRole == 'admin',
onSelected: (_) {
setSheetState(() {
selectedMemberRole = 'admin';
});
},
),
ChoiceChip(
label: const Text('メンバー'),
selected: selectedMemberRole == 'member',
onSelected: (_) {
setSheetState(() {
selectedMemberRole = 'member';
});
},
),
ChoiceChip(
label: const Text('閲覧者'),
selected: selectedMemberRole == 'viewer',
onSelected: (_) {
setSheetState(() {
selectedMemberRole = 'viewer';
});
},
),
],
)
4. 保存時に使う
'role': selectedMemberRole,
チェックリスト
□ main.dartを開いた
□ MemberListPageを探した
□ selectedMemberRoleを追加した
□ 初期値をmemberにした
□ showAddMemberSheetで初期化した
□ ChoiceChipを3つ作った
□ 管理者チップを作った
□ メンバーチップを作った
□ 閲覧者チップを作った
□ setSheetStateで選択を変更した
□ role保存をselectedMemberRoleに変更した
□ 保存した
□ flutter runで確認した
□ チップを選択できた
□ 選んだroleでFirestoreに保存された
ミニ確認問題
Q1. ChoiceChipは何のために使いますか?
回答
複数の選択肢から、1つを選ぶために使います。
今回なら、メンバーの権限を選ぶために使います。
Q2. 追加時に選べるroleは何ですか?
回答
admin、member、viewer です。
Q3. なぜownerは選択肢に入れませんか?
回答
owner はチームの最上位権限で、簡単に増やすと危険だからです。
Q4. Firestoreに保存するroleはどの変数を使いますか?
回答
selectedMemberRole を使います。
'role': selectedMemberRole
Q5. このページでnpmや環境変数は必要ですか?
回答
必要ありません。
Flutterの標準Widget ChoiceChip を使うだけです。
このページのまとめ
ChoiceChipで権限を選べるUIを作る。- 選択肢は
admin、member、viewer。 - 表示は日本語、保存は英語にする。
- 初期値は
memberにする。 - チップを押したら
selectedMemberRoleを変更する。 - 保存時は
'role': selectedMemberRoleにする。 ownerは追加画面では選ばせない。- このページではnpmや環境変数は不要。
次のページでやること
次のページでは、既存メンバーの権限を変更できるようにします。
メンバー一覧からユーザーをタップして、admin・member・viewer を変更する画面を作ります。
