TEXTBOOK SECTION / AI LEARNING

【スワイプ削除】Dismissibleでチームを左スワイプ削除できるようにする

Flutterアプリケーション開発概論の「LINE風チームタスク管理アプリを作りながら、ログイン・データベース・権限管理を学ぶ」より、【スワイプ削除】Dismissibleでチームを左スワイプ削除できるようにするを解説。生成AI、AI活用、DX、業務改善を実践しながら学べるオンライン教材です。

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

OVERVIEW

この節で学べること

概要を表示する
項目内容
教材名Flutterアプリケーション開発概論
LINE風チームタスク管理アプリを作りながら、ログイン・データベース・権限管理を学ぶ
【スワイプ削除】Dismissibleでチームを左スワイプ削除できるようにする
カテゴリFlutter / iOS / Android / MacOS / Windows / 基礎から学ぶ / 開発 / アプリ開発
学習内容生成AI、AI活用、DX、業務改善を実践しながら理解するための教材です。

TABLE OF CONTENTS

目次

CONTENT

ここから

このページでやること

このページでは、トーク一覧に表示されているチームを、左スワイプで削除できるようにします。

Flutterでは、スワイプ削除に Dismissible というWidgetを使います。

チーム一覧
↓
チームカードを左にスワイプ
↓
削除確認
↓
Firestoreからチームを削除
↓
一覧から消える

このページでは、まずシンプルに削除できるところまで作ります。


今日のゴール

チームカードを左へスワイプすると、赤い削除背景が出て、チームを削除できるようにします。

開発チーム  ← 左にスワイプ
↓
削除
↓
一覧から消える

削除対象は、Firestoreのここです。

teams/{teamId}

このページで出てくる単語

単語一言説明
Dismissibleスワイプで項目を消せるWidget
directionスワイプできる方向
endToStart右から左へスワイプする方向
confirmDismiss本当に削除してよいか確認する処理
onDismissedスワイプ後に実行される処理
delete()Firestoreのデータを削除する命令
Key一覧の1件1件を区別するための印

Widget とは、Flutterの画面部品のことです。


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

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

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

必要なのは、すでに使っているFirestoreです。

import 'package:cloud_firestore/cloud_firestore.dart';

このページでやることは、TeamCardDismissible で包むことです。


Step 1:main.dartを開く

次のファイルを開きます。

lib/main.dart

ターミナルから開く場合は、次を実行します。

code lib/main.dart

Step 2:TeamListPageを探す

main.dart の中で、次を検索します。

class TeamListPage

VS Codeなら、次で検索できます。

command + F

この中にある ListView.separated を変更します。


Step 3:今のTeamCard表示を確認する

前のページでは、チーム一覧の1件を次のように表示していました。

return TeamCard(
  name: name,
  onTap: () {
    // 次のページでタスク一覧画面へ移動します
  },
);

今回は、この TeamCardDismissible で包みます。


Step 4:チーム削除処理を作る

まず、_TeamListPageState の中に、次の関数を追加します。

Future<void> deleteTeam(String teamId) async {
  await FirebaseFirestore.instance.collection('teams').doc(teamId).delete();
}

これは、Firestoreの teams/{teamId} を削除する処理です。

teamId は、削除したいチームのIDです。


Step 5:削除確認ダイアログを作る

いきなり削除すると危ないので、確認画面を出します。

_TeamListPageState の中に、次の関数を追加します。

Future<bool> confirmDeleteTeam(String teamName) async {
  final result = await showDialog<bool>(
    context: context,
    builder: (context) {
      return AlertDialog(
        title: const Text('チームを削除しますか?'),
        content: Text('「$teamName」を削除します。'),
        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;
}

showDialog は、確認用の小さな画面を出す命令です。

true が返ったら削除します。

false なら削除しません。


Step 6:TeamCardをDismissibleで包む

ListView.separated の中で、次の部分を探します。

return TeamCard(
  name: name,
  onTap: () {
    // 次のページでタスク一覧画面へ移動します
  },
);

これを、次のコードに置き換えます。

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(14),
    ),
    child: const Icon(
      Icons.delete,
      color: Colors.white,
    ),
  ),
  confirmDismiss: (_) async {
    return confirmDeleteTeam(name);
  },
  onDismissed: (_) async {
    await deleteTeam(doc.id);
  },
  child: TeamCard(
    name: name,
    onTap: () {
      // 次のページでタスク一覧画面へ移動します
    },
  ),
);

これで、チームを左スワイプできるようになります。


Step 7:Dismissibleの意味を見る

今回の中心はここです。

Dismissible(
  key: ValueKey(doc.id),
  direction: DismissDirection.endToStart,
  ...
)

Dismissible は、スワイプで消せる一覧項目を作るWidgetです。

key は、一覧の中の1件を区別するための印です。

今回は、FirestoreのドキュメントIDを使います。

key: ValueKey(doc.id),

doc.id は、FirestoreのチームIDです。


Step 8:左スワイプだけにする

この部分です。

direction: DismissDirection.endToStart,

endToStart は、右から左へスワイプする方向です。

日本語の画面では、左スワイプ削除のイメージになります。

右 → 左

間違えて右スワイプでも消えないように、方向を限定しています。


Step 9:赤い削除背景を作る

この部分です。

background: Container(
  alignment: Alignment.centerRight,
  padding: const EdgeInsets.only(right: 20),
  decoration: BoxDecoration(
    color: AppColors.danger,
    borderRadius: BorderRadius.circular(14),
  ),
  child: const Icon(
    Icons.delete,
    color: Colors.white,
  ),
),

スワイプしたときに、赤い背景と削除アイコンが出ます。

alignment: Alignment.centerRight にすることで、右側に削除アイコンを表示します。


Step 10:削除前に確認する

この部分です。

confirmDismiss: (_) async {
  return confirmDeleteTeam(name);
},

confirmDismiss は、スワイプ後に本当に削除してよいか確認する場所です。

ここで true が返ると、削除が進みます。

false が返ると、元に戻ります。

左スワイプ
↓
確認画面
↓
削除する → true
キャンセル → false

Step 11:Firestoreから削除する

この部分です。

onDismissed: (_) async {
  await deleteTeam(doc.id);
},

onDismissed は、スワイプ削除が確定したあとに動きます。

ここで、Firestoreのチームを削除します。

await FirebaseFirestore.instance.collection('teams').doc(teamId).delete();

Step 12:保存する

main.dart を保存します。

Macの場合:

command + S

Windowsの場合:

Ctrl + S

Step 13:実行する

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

flutter run

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

r

動きがおかしい場合は、R でホットリスタートします。

R

Step 14:スワイプ削除を試す

ログイン後、トーク一覧画面を開きます。

チームカードを左へスワイプします。

チームカード
↓
左へスワイプ
↓
赤い削除背景が出る
↓
確認画面が出る
↓
削除する

削除後、一覧からチームが消えれば成功です。


Step 15:Firestoreで確認する

Firebase Consoleを開きます。

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

次の順番で確認します。

Firestore Database
↓
データ
↓
teams

削除したチームのドキュメントが消えていればOKです。


注意:membersやtasksはどうなる?

今回のコードでは、teams/{teamId} だけを削除します。

ただし、Firestoreでは、親ドキュメントを削除しても、サブコレクションが自動で完全削除されるとは限りません。

つまり、次のデータが残る場合があります。

teams/{teamId}/members
teams/{teamId}/tasks

学習段階では、まず teams/{teamId} を削除できればOKです。

本番アプリでは、Cloud Functionsや管理用処理で、関連する memberstasks も整理する設計にします。


よくあるエラーと直し方

エラー原因直し方
Dismissible が使えないMaterialのimport不足import 'package:flutter/material.dart'; を確認
doc.id が使えないdoc の場所が違うfinal doc = teams[index]; の下で使う
スワイプできないDismissible で包んでいないTeamCardDismissible で包む
右スワイプでも消えるdirection未指定DismissDirection.endToStart を指定
削除後にエラーFirestore Rulesで拒否開発用Rulesを確認
削除後に復活するFirestore削除に失敗しているdeleteTeam(doc.id) を確認
確認画面が出ないconfirmDismiss がないconfirmDismiss を追加

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

開発中だけ、Firestore Rulesを次のようにして確認できます。

rules_version = '2';

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

これは学習用です。

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

本番では、owneradmin だけがチームを削除できるようにします。


TeamCardだけ消えてFirestoreに残る場合

confirmDismiss は動いているが、Firestore削除が失敗している可能性があります。

この部分を確認してください。

onDismissed: (_) async {
  await deleteTeam(doc.id);
},

そして、deleteTeam() が次のようになっているか確認します。

Future<void> deleteTeam(String teamId) async {
  await FirebaseFirestore.instance.collection('teams').doc(teamId).delete();
}

本番では権限チェックが必要

この教材では、まず動きを理解するために、誰でも削除できる形で進めています。

本番では、必ず権限チェックを入れます。

ownerだけ削除できる
adminだけ削除できる
memberは削除できない
viewerは削除できない

権限チェックは、後のSecurity Rulesのページで扱います。


最短作業まとめ

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

1. 削除処理を追加

Future<void> deleteTeam(String teamId) async {
  await FirebaseFirestore.instance.collection('teams').doc(teamId).delete();
}

2. 削除確認を追加

Future<bool> confirmDeleteTeam(String teamName) async {
  final result = await showDialog<bool>(
    context: context,
    builder: (context) {
      return AlertDialog(
        title: const Text('チームを削除しますか?'),
        content: Text('「$teamName」を削除します。'),
        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;
}

3. TeamCardをDismissibleで包む

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(14),
    ),
    child: const Icon(
      Icons.delete,
      color: Colors.white,
    ),
  ),
  confirmDismiss: (_) async {
    return confirmDeleteTeam(name);
  },
  onDismissed: (_) async {
    await deleteTeam(doc.id);
  },
  child: TeamCard(
    name: name,
    onTap: () {
      // 次のページでタスク一覧画面へ移動します
    },
  ),
);

4. 保存して実行

flutter run

チェックリスト

□ main.dartを開いた
□ TeamListPageを探した
□ deleteTeam()を作った
□ confirmDeleteTeam()を作った
□ TeamCardをDismissibleで包んだ
□ keyにValueKey(doc.id)を指定した
□ directionにendToStartを指定した
□ 赤い削除背景を作った
□ confirmDismissを追加した
□ onDismissedでdeleteTeam(doc.id)を呼んだ
□ 保存した
□ flutter runで起動した
□ 左スワイプで削除確認が出た
□ 削除後に一覧から消えた

ミニ確認問題

Q1. Dismissible は何をするWidgetですか?

回答

一覧の1件を、スワイプで削除できるようにするWidgetです。


Q2. DismissDirection.endToStart はどの方向ですか?

回答

右から左へのスワイプです。

今回の左スワイプ削除に使います。


Q3. confirmDismiss は何のために使いますか?

回答

本当に削除してよいか、削除前に確認するために使います。

true を返すと削除、false を返すとキャンセルです。


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

回答

必要ありません。

このページでは、Flutterの Dismissible とFirestoreの delete() を使うだけです。


このページのまとめ

  • Dismissible を使うと、一覧項目をスワイプ削除できる。
  • direction: DismissDirection.endToStart で左スワイプに限定できる。
  • background で赤い削除背景を作る。
  • confirmDismiss で削除前に確認できる。
  • onDismissed でFirestoreから削除する。
  • 今回は teams/{teamId} を削除する。
  • 本番では owneradmin だけが削除できるようにする。
  • このページではnpmや環境変数は不要。

次のページでやること

次のページでは、チームをタップしてタスク一覧画面へ移動します。

teamIdteamName を渡して、そのチーム専用のタスク一覧を表示する準備をします。

FAQ

よくある質問

【スワイプ削除】Dismissibleでチームを左スワイプ削除できるようにするは医療関係者向けだけの内容ですか。
医療分野の例が含まれる場合もありますが、医療関係者だけに限定した内容ではありません。生成AI、AI活用、DX、業務改善、プロトタイプ開発など、一般的なAI学習の事例として読める内容です。
AI初心者でも読めますか。
はい。AIをこれから学ぶ方、数学が苦手な方、仕事でAIを使いたい方にも読み進めやすいように、教材の章と節の流れに沿って整理しています。
サムネイル画像は必ず表示されますか。
はい。教材にcoverUrlが設定されている場合はその画像を表示し、未設定の場合は代替サムネイル画像を表示します。
Flutterアプリケーション開発概論のほかの章も読めますか。
はい。教材トップから章立てを確認でき、前後の節へもページ下部のナビゲーションから移動できます。