第20章:総合ミニ課題(ToDo/メモ完成)+AIで“実用っぽさ”を足す🎯🤖✨
この章はゴールがシンプルです👇 「CRUD+リアルタイム+クエリ+ページング」が一通り動く ToDo/メモアプリを“完成品”にする&AIで便利さを一段上げる🤖💨
1) この章で“完成”にする機能一覧 ✅
必須(ここまでの総復習)🧠✨
- 追加 / 編集 / 削除 ✅
- 一覧はリアルタイム更新 ⚡(勝手に増えるやつ)
- 未完了だけ表示(フィルタ)🔎
- ページング(もっと読む or 無限スクロール)📜♾️
createdAt/updatedAtを入れてソート&更新管理 ⏱️
AIで“実用っぽさ”を足す(今回の主役)🤖🌟
- ✨ AI整形:メモ本文を「読みやすく整える」
- 🏷️ AIタグ抽出:本文からタグ(配列)を作って
tags: string[]に保存 - 🧾(おまけ)AIタイトル提案:本文から短いタイトルを提案して
titleに反映
このAI部分は Firebase AI Logic を使って、アプリから Gemini を安全に呼ぶ流れにします。(Firebase)
※モデルは gemini-2.5-flash-lite など新しめ推奨。古い gemini-2.0-flash 系は 2026-03-31 でリタイア予定が明記されています(地味に重要⚠️)。(Firebase)
2) データ設計(迷子防止)🧭🗃️

コレクション:todos
ドキュメント(例):
export type Todo = {
title: string; // 表示用タイトル
body?: string; // メモ本文(任意)
done: boolean; // 完了フラグ
tags: string[]; // AI抽出タグ(空配列OK)
createdAt: any; // Timestamp(Firestore)
updatedAt: any; // Timestamp(Firestore)
};
(createdAt/updatedAt を Timestamp にするのがソート&ページングの基本になります⏱️)
3) “完成品っぽくする”実装ステップ(順番が大事)🧩
Step A:一覧(リアルタイム)+フィルタ(未完了だけ)🔎⚡
- 一覧は基本
onSnapshot()で購読 - フィルタは
where("done", "==", false)を切り替えるだけ🎛️ - ソートは
orderBy("createdAt", "desc")で新しい順⬆️
ポイント👀
- フィルタを変えたら購読を張り替える(unsubscribe 忘れがち🧯)
Step B:ページング(“リアルタイム+追加読み”の現実解)📜✨

Firestoreのページングは「カーソル」方式です(startAfter(lastDoc))📌(Firebase)
**おすすめ構成(初心者に優しい&破綻しにくい)**👇
- 先頭のN件だけリアルタイム(最新が勝手に反映⚡)
- “もっと読む”で古い分を追加取得(ここは
getDocsでOK)
これで「リアルタイム全部+無限スクロール全部」を無理に混ぜて爆発💥しにくいです。
ざっくり実装イメージ(要点だけ):
const PAGE_SIZE = 10;
// ① 先頭ページ(リアルタイム)
const headQuery = query(
collection(db, "todos"),
where("done", "==", false), // フィルタON時だけ
orderBy("createdAt", "desc"),
limit(PAGE_SIZE)
);
// ② 追加ページ(もっと読む)
const nextQuery = query(
collection(db, "todos"),
where("done", "==", false),
orderBy("createdAt", "desc"),
startAfter(lastDoc),
limit(PAGE_SIZE)
);
(カーソル式ページングの基本はこの形です。)(Firebase)
Step C:AI Logic を組み込む(今回の目玉🤖🔥)
ここは Firebase JS SDK v12.9.0 で入っている firebase/ai を使うのがポイントです。(Google for Developers)
1) まずは AI の初期化(1ファイルにまとめる)🧰
例:src/lib/ai.ts
import { app } from "./firebaseApp"; // 既に作ってある初期化を想定
import { getAI, getGenerativeModel, GoogleAIBackend } from "firebase/ai";
export const ai = getAI(app, { backend: new GoogleAIBackend() });
// ★モデル名はあとで差し替えやすいように定数に
export const GEMINI_MODEL = "gemini-2.5-flash-lite";
export const model = getGenerativeModel(ai, {
model: GEMINI_MODEL,
});
(getAI / GoogleAIBackend / getGenerativeModel の形は公式Getting Startedに沿っています。)(Firebase)
2) AI整形:本文を読みやすくする ✨📝

例:src/lib/aiEnhance.ts
import { model } from "./ai";
export async function aiPolishBody(body: string): Promise<string> {
const prompt =
"次のメモを、意味は変えずに読みやすい日本語に整えてください。"
+ "出力は整形後の本文だけ(余計な前置き/箇条書き/解説なし)。\n\n"
+ body;
const result = await model.generateContent(prompt);
return result.response.text().trim();
}
(まずは“テキストを返すだけ”でOK。ここで複雑にしないのがコツ😺)
UIはこういう感じが作りやすいです👇
- テキストエリアの下に 「AI整形」ボタン
- 押したらスピナー表示→整形後の本文で置き換え→「保存」へ✅
3) AIタグ抽出:JSONで返させて tags: string[] にする 🏷️🧾

ここは事故が起きやすいので、**“構造化出力(JSON)”**の考え方でガードします。(Firebase)
初心者向けに「まずは堅いプロンプト」でやる版👇
import { model } from "./ai";
export async function aiExtractTags(body: string): Promise<string[]> {
const prompt =
"次のメモ本文から、タグを3〜8個抽出してください。\n"
+ "条件:\n"
+ "- 出力はJSONのみ\n"
+ "- 形式は {\"tags\": [\"...\", \"...\"]}\n"
+ "- タグは短い名詞、重複なし、日本語\n\n"
+ body;
const result = await model.generateContent(prompt);
const text = result.response.text().trim();
// ゆるくパース(失敗したら空配列にする)
try {
const obj = JSON.parse(text);
const tags = Array.isArray(obj.tags) ? obj.tags : [];
return tags
.filter((x) => typeof x === "string")
.map((x) => x.trim())
.filter((x) => x.length > 0)
.slice(0, 8);
} catch {
return [];
}
}
ポイントはこれ👇
- JSON以外を許さない(雑談で返されると壊れるので🧯)
- 失敗してもアプリが落ちない(空配列で逃がす😺)
Step D:AI結果をFirestoreに保存する(安全に)✅🗃️
保存時は「AIで整形→タグ抽出→まとめて保存」みたいに一気にやると気持ちいいです✨
例(流れだけ):
const polished = await aiPolishBody(body);
const tags = await aiExtractTags(polished);
await updateDoc(doc(db, "todos", id), {
body: polished,
tags,
updatedAt: serverTimestamp(),
});
4) “公開しても恥ずかしくない”ひと工夫(軽め)🧯✨
✅ AI呼び出しの濫用対策(重要)

AIは便利だけど、呼ばれ放題だとコストも事故も増えます💸😇 Firebase AI Logicの本番チェック項目としても、保護や運用面がまとまっています。(Firebase)
おすすめガード👇
- AIボタンは 連打不可(処理中はdisabled)
- 1回の整形で最大文字数を制限(例:2000文字まで)✂️
- App Check を有効化して、勝手な呼び出しを減らす🛡️(できたらやる)(Firebase)
- モデル名はコード直書きじゃなく 後から差し替え可能に(Remote Configなど)🔧(Firebase)
5) 発展(任意):サーバー側で自動化したい人へ ☁️🧠
「保存した瞬間に、サーバー側で自動タグ付けしたい!」みたいな発展もあります🔥
- Cloud Functions for Firebase(Node):Node.js 20/22 がサポートとして案内されています。(Stack Overflow)
- Cloud Functions for Firebase(Python):Python 3.12 で動かす流れが出てきます(CLIのリリースノートにも言及あり)。(Firebase)
- C#でやりたい:Firebase Functions本体というより、Google Cloudの 2nd genで .NET 8(Cloud Run functions/Cloud Functions)側で組むのが現実的です。(Google Cloud Documentation)
サーバーから Firestore を触るなら Admin SDK が便利で、現時点の目安は👇
6) AIで“開発そのもの”を速くする(Antigravity / Gemini CLI)🚀🤖
実装が詰まったら、ここがめちゃ効きます。
Antigravity(設計→実装の段取りを作る)🛰️

- 「この章の要件をToDoに分解して」
- 「今のファイル構成に合わせて差分パッチ案を出して」 みたいな使い方がハマります(Mission Control的な進め方の例あり)。
Gemini CLI(ターミナルで調査&修正支援)⌨️✨
- 「このエラーの原因と直し方、最短で」
- 「このhooksのメモリリークの可能性ある?」
- 「この関数、型安全にリファクタして」 みたいな“雑に聞いて、速く直す”がやりやすいです。(LinkedIn)
7) ミニ課題(提出物イメージ)🧩🎯
最低ラインはこれでOK👇
- ToDo/メモが CRUD できる✅
- 一覧がリアルタイムで増える⚡
- 未完了フィルタが効く🔎
- “もっと読む”で追加ロードできる📜
- AI整形ボタンで本文が整う✨
- AIタグ抽出ボタンで
tagsが入る🏷️
余裕があれば✨
- ローディング/空状態/エラー状態の表示が綺麗
- AI処理中はボタン無効&進捗表示
- App Check / モデル差し替え(Remote Config)まで到達🛡️🔧
8) 最終チェックリスト(自己採点)✅✅✅
-
createdAt descで新しい順になってる? - 別タブで追加しても一覧が即更新される?⚡
- フィルタ切替で購読が二重になってない?(メモリリークなし)🧯
- 追加ロードで重複表示しない?
- AIの返答が変でもアプリが落ちない?(JSONパース失敗→空配列など)
- 2.0系モデル名を固定してない?(3/31問題⚠️)(Firebase)
必要なら、この第20章の内容を **「完成版のファイル構成(src/ 配下)+コピペで動く最小実装」**として、useTodos() / TodoEditor / AIボタン周り まで一気に“完成形コード”にして出します😺📦