【タスク削除】admin以上の権限でタスクを削除できるようにする
このページでやること
このページでは、タスクを削除できるようにします。
ただし、誰でも削除できるのではなく、チーム内の権限が admin 以上の人だけ削除できるようにします。
今回の考え方は、こうです。
owner → 削除できる
admin → 削除できる
member → 削除できない
viewer → 削除できない
タスクの保存場所はここです。
teams/{teamId}/tasks/{taskId}
このタスクを、左スワイプで削除します。
今日のゴール
タスク一覧画面で、タスクを左スワイプしたときに、権限を確認して削除できるようにします。
タスクを左スワイプ
↓
自分のroleを確認
↓
owner または admin なら削除OK
↓
member または viewer なら削除NG
このページでは、Flutter側で権限チェックを入れます。
本番では、あとでFirestore Security Rules側にも必ず権限チェックを入れます。
このページで出てくる単語
| 単語 | 一言説明 |
|---|---|
role | チーム内での権限 |
owner | チームの所有者 |
admin | 管理者 |
member | 一般メンバー |
viewer | 閲覧のみ |
Dismissible | スワイプ削除できるWidget |
delete() | Firestoreのデータを削除する命令 |
confirmDismiss | 削除前に確認する処理 |
npmや環境変数は必要?
このページでは、npmは使いません。
環境変数も設定しません。
必要なのは、すでに使っているこの2つです。
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
このページでは、TaskListPage に削除処理を追加します。
Step 1:main.dartを開く
次のファイルを開きます。
lib/main.dart
ターミナルから開く場合はこちらです。
code lib/main.dart
Step 2:TaskListPageを探す
main.dart の中で、次を検索します。
class TaskListPage
VS Codeなら、次で検索できます。
command + F
この TaskListPage の中に、タスク削除処理を追加します。
Step 3:自分のroleを取得する関数を作る
まず、ログイン中ユーザーのチーム内権限を取得します。
_TaskListPageState の中に、次の関数を追加します。
Future<String?> getMyRole() async {
final user = FirebaseAuth.instance.currentUser;
if (user == null) {
return null;
}
final memberDoc = await FirebaseFirestore.instance
.collection('teams')
.doc(widget.teamId)
.collection('members')
.doc(user.uid)
.get();
final data = memberDoc.data();
return data?['role']?.toString();
}
この関数は、次の場所を見に行きます。
teams/{teamId}/members/{uid}
そこに保存されている role を取得します。
Step 4:admin以上か確認する関数を作る
次に、取得した role が削除できる権限かどうかを判定します。
_TaskListPageState の中に、次の関数を追加します。
bool canDeleteTask(String? role) {
return role == 'owner' || role == 'admin';
}
意味はこうです。
role が owner なら true
role が admin なら true
それ以外は false
true は削除できるという意味です。
false は削除できないという意味です。
Step 5:タスク削除処理を作る
次に、Firestoreからタスクを削除する処理を作ります。
_TaskListPageState の中に、次の関数を追加します。
Future<void> deleteTask(String taskId) async {
await FirebaseFirestore.instance
.collection('teams')
.doc(widget.teamId)
.collection('tasks')
.doc(taskId)
.delete();
}
これは、次の場所を削除する処理です。
teams/{teamId}/tasks/{taskId}
Step 6:削除確認ダイアログを作る
いきなり削除すると危ないので、確認画面を出します。
_TaskListPageState の中に、次の関数を追加します。
Future<bool> confirmDeleteTask(String taskTitle) async {
final result = await showDialog<bool>(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('タスクを削除しますか?'),
content: Text('「$taskTitle」を削除します。'),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text('キャンセル'),
),
FilledButton(
onPressed: () => Navigator.of(context).pop(true),
style: FilledButton.styleFrom(
backgroundColor: AppColors.danger,
),
child: const Text('削除する'),
),
],
);
},
);
return result == true;
}
「削除する」を押すと true。
「キャンセル」を押すと false になります。
Step 7:権限がないときのメッセージを作る
member や viewer が削除しようとしたときは、メッセージを出します。
_TaskListPageState の中に、次の関数を追加します。
void showNoPermissionMessage() {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('タスクを削除できるのはownerまたはadminのみです。'),
),
);
}
SnackBar は、画面下に一時的に出るメッセージです。
Step 8:TaskCardをDismissibleで包む
TaskListPage の中で、TaskCard を返している場所を探します。
今は、次のような形になっているはずです。
return TaskCard(
title: title,
description: description,
status: status,
priority: priority,
assigneeEmail: assigneeEmail,
onTap: () {
showEditTaskSheet(
taskId: doc.id,
currentTitle: title,
currentDescription: description,
currentAssigneeEmail: assigneeEmail,
currentPriority: priority,
currentStatus: status,
);
},
);
これを、次のコードに変更します。
return Dismissible(
key: ValueKey(doc.id),
direction: DismissDirection.endToStart,
background: Container(
alignment: Alignment.centerRight,
padding: const EdgeInsets.only(right: 20),
decoration: BoxDecoration(
color: AppColors.danger,
borderRadius: BorderRadius.circular(18),
),
child: const Icon(
Icons.delete,
color: Colors.white,
),
),
confirmDismiss: (_) async {
final role = await getMyRole();
if (!canDeleteTask(role)) {
showNoPermissionMessage();
return false;
}
return confirmDeleteTask(title);
},
onDismissed: (_) async {
await deleteTask(doc.id);
},
child: TaskCard(
title: title,
description: description,
status: status,
priority: priority,
assigneeEmail: assigneeEmail,
onTap: () {
showEditTaskSheet(
taskId: doc.id,
currentTitle: title,
currentDescription: description,
currentAssigneeEmail: assigneeEmail,
currentPriority: priority,
currentStatus: status,
);
},
),
);
これで、左スワイプ削除に権限チェックが入ります。
Step 9:confirmDismissの流れを確認する
今回の一番大事な部分はここです。
confirmDismiss: (_) async {
final role = await getMyRole();
if (!canDeleteTask(role)) {
showNoPermissionMessage();
return false;
}
return confirmDeleteTask(title);
},
流れはこうです。
左スワイプする
↓
自分のroleを取得する
↓
owner/admin か確認する
↓
権限なし → 削除しない
↓
権限あり → 確認ダイアログを出す
return false にすると、削除されません。
return true になると、削除が進みます。
Step 10:onDismissedでFirestoreから削除する
この部分で、Firestoreからタスクを削除します。
onDismissed: (_) async {
await deleteTask(doc.id);
},
doc.id は、タスクのIDです。
つまり、次の {taskId} にあたります。
teams/{teamId}/tasks/{taskId}
Step 11:保存する
main.dart を保存します。
Macの場合:
command + S
Windowsの場合:
Ctrl + S
Step 12:実行する
ターミナルで実行します。
flutter run
すでに起動している場合は、ターミナルで r を押します。
r
うまく反映されない場合は、R を押します。
R
Step 13:ownerで削除を確認する
まず、チーム作成者でログインします。
チーム作成者は owner になっています。
トーク一覧
↓
チームをタップ
↓
タスク一覧
↓
タスクを左スワイプ
↓
削除する
タスクが一覧から消えれば成功です。
Step 14:adminで確認する
Firestoreで、自分の role を admin に変えると確認できます。
Firebase Consoleで、次の場所を開きます。
Firestore Database
↓
teams
↓
作成したteamId
↓
members
↓
自分のuid
role を次のように変更します。
role: admin
アプリに戻り、タスクを左スワイプします。
削除できればOKです。
Step 15:memberで削除できないことを確認する
同じ場所で、role を次のように変更します。
role: member
アプリに戻り、タスクを左スワイプします。
次のメッセージが出て、削除されなければ成功です。
タスクを削除できるのはownerまたはadminのみです。
Step 16:viewerで削除できないことを確認する
最後に、role を次のように変更します。
role: viewer
タスクを左スワイプして、削除できないことを確認します。
viewer → 削除できない
これで、権限チェックの基本が確認できます。
よくあるエラーと直し方
| エラー | 原因 | 直し方 |
|—|—|
| getMyRole isn't defined | 関数がない | getMyRole() を追加 |
| canDeleteTask isn't defined | 関数がない | canDeleteTask() を追加 |
| deleteTask isn't defined | 関数がない | deleteTask() を追加 |
| 左スワイプできない | Dismissible で包んでいない | TaskCard を Dismissible で包む |
| 権限なしでも削除される | confirmDismiss がない | confirmDismiss でrole確認する |
| role がnullになる | members/{uid} がない | Firestoreでmembersを確認 |
| permission-denied | Firestore Rulesで拒否 | 開発用Rulesを確認 |
roleがnullになるとき
role が取れないときは、Firestoreを確認します。
teams
↓
作成したteamId
↓
members
↓
自分のuid
中に、次があるか確認してください。
role: owner
または、
role: admin
members/{uid} がない場合、チーム作成時のowner登録ができていない可能性があります。
permission-denied が出たとき
開発中だけ、Firestore Rulesを次のようにして確認できます。
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if true;
}
}
}
これは学習用です。
本番公開では使わないでください。
本番では、Flutter側だけでなく、Firestore Rules側でも owner / admin だけ削除できるようにします。
注意:Flutter側の権限チェックだけでは不十分
このページでは、Flutterの画面側で削除を止めています。
ただし、本番ではこれだけでは不十分です。
理由は、アプリの画面を通さずにFirestoreへ直接アクセスされる可能性があるからです。
そのため、本番では必ずFirestore Security Rulesにも制限を書きます。
Flutter側
↓
ユーザーに分かりやすく制御する
Firestore Rules側
↓
本当にデータを守る
この教材では、まずFlutter側で動きを理解します。
最短作業まとめ
読むのが大変な人は、ここだけ見てください。
1. 自分のroleを取得
Future<String?> getMyRole() async {
final user = FirebaseAuth.instance.currentUser;
if (user == null) {
return null;
}
final memberDoc = await FirebaseFirestore.instance
.collection('teams')
.doc(widget.teamId)
.collection('members')
.doc(user.uid)
.get();
final data = memberDoc.data();
return data?['role']?.toString();
}
2. admin以上か確認
bool canDeleteTask(String? role) {
return role == 'owner' || role == 'admin';
}
3. タスクを削除
Future<void> deleteTask(String taskId) async {
await FirebaseFirestore.instance
.collection('teams')
.doc(widget.teamId)
.collection('tasks')
.doc(taskId)
.delete();
}
4. Dismissibleで包む
return Dismissible(
key: ValueKey(doc.id),
direction: DismissDirection.endToStart,
confirmDismiss: (_) async {
final role = await getMyRole();
if (!canDeleteTask(role)) {
showNoPermissionMessage();
return false;
}
return confirmDeleteTask(title);
},
onDismissed: (_) async {
await deleteTask(doc.id);
},
child: TaskCard(
title: title,
description: description,
status: status,
priority: priority,
assigneeEmail: assigneeEmail,
onTap: () {
showEditTaskSheet(
taskId: doc.id,
currentTitle: title,
currentDescription: description,
currentAssigneeEmail: assigneeEmail,
currentPriority: priority,
currentStatus: status,
);
},
),
);
チェックリスト
□ main.dartを開いた
□ TaskListPageを探した
□ getMyRole()を作った
□ canDeleteTask()を作った
□ deleteTask()を作った
□ confirmDeleteTask()を作った
□ showNoPermissionMessage()を作った
□ TaskCardをDismissibleで包んだ
□ directionをendToStartにした
□ confirmDismissでroleを確認した
□ owner/adminなら削除できるようにした
□ member/viewerなら削除できないようにした
□ onDismissedでdeleteTask(doc.id)を呼んだ
□ 保存した
□ flutter runで確認した
ミニ確認問題
Q1. タスクを削除できる権限はどれですか?
回答
owner と admin です。
Q2. member はタスクを削除できますか?
回答
できません。
Q3. 自分の権限はFirestoreのどこから取得しますか?
回答
次の場所から取得します。
teams/{teamId}/members/{uid}
Q4. タスク削除で使うFirestoreの命令は何ですか?
回答
delete() です。
Q5. このページでnpmや環境変数は必要ですか?
回答
必要ありません。
このページでは、FlutterとFirestoreの既存設定だけで進めます。
このページのまとめ
- タスク削除には
Dismissibleを使う。 - 左スワイプ削除は
DismissDirection.endToStartを使う。 - 自分の権限は
teams/{teamId}/members/{uid}から取得する。 ownerとadminは削除できる。memberとviewerは削除できない。- Flutter側の権限チェックは、ユーザー体験のために入れる。
- 本番ではFirestore Security Rules側にも必ず制限を書く。
- このページではnpmや環境変数は不要。
次のページでやること
次のページでは、メンバー一覧画面を作ります。
teams/{teamId}/members を取得して、チームに参加しているユーザーを表示します。
