レシピ#4-5: Cursor + Claudeでシンプルなアプリを作る
はじめに
これまで学んだ内容を実践します。Cursor + Claudeを使って、シンプルだけど実用的なアプリを作りましょう。
今回作るアプリ: メモアプリ
機能: - メモの追加 - メモ一覧表示 - メモの編集 - メモの削除 - データの永続化(SharedPreferences)
技術スタック: - Flutter 3.27 - StatefulWidget(状態管理) - SharedPreferences(データ保存) - Serviceクラス(ビジネスロジック)
避けるもの: - ❌ Riverpod - ❌ BLoC - ❌ 複雑なアーキテクチャ
Step 1: プロジェクト作成
flutter create memo_app
cd memo_app
cursor .
Step 2: データ構造を設計(Cursorに依頼)
プロンプト:
メモアプリのデータ構造を設計してください。
必要な情報:
- ID (UUID)
- タイトル
- 内容
- 作成日時
- 更新日時
以下を含めてください:
1. Memoクラスの定義
2. JSONシリアライゼーション(toJson/fromJson)
3. サンプルデータ
注意: freezedなどの複雑なパッケージは使わない
Claudeの回答例:
import 'package:uuid/uuid.dart';
class Memo {
final String id;
final String title;
final String content;
final DateTime createdAt;
final DateTime updatedAt;
Memo({
String? id,
required this.title,
required this.content,
DateTime? createdAt,
DateTime? updatedAt,
}) : id = id ?? const Uuid().v4(),
createdAt = createdAt ?? DateTime.now(),
updatedAt = updatedAt ?? DateTime.now();
// JSONから変換
factory Memo.fromJson(Map<String, dynamic> json) {
return Memo(
id: json['id'] as String,
title: json['title'] as String,
content: json['content'] as String,
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
);
}
// JSONに変換
Map<String, dynamic> toJson() {
return {
'id': id,
'title': title,
'content': content,
'created_at': createdAt.toIso8601String(),
'updated_at': updatedAt.toIso8601String(),
};
}
// コピーして更新
Memo copyWith({
String? title,
String? content,
}) {
return Memo(
id: id,
title: title ?? this.title,
content: content ?? this.content,
createdAt: createdAt,
updatedAt: DateTime.now(),
);
}
}
Step 3: Serviceクラス作成
プロンプト:
MemoServiceクラスを作成してください。
機能:
- メモ一覧の取得
- メモの追加
- メモの更新
- メモの削除
- SharedPreferencesで永続化
構成:
- シングルトンパターン
- エラーハンドリング付き
- 先ほど定義したMemoクラスを使用
Claudeの回答例:
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
class MemoService {
static final MemoService instance = MemoService._internal();
MemoService._internal();
factory MemoService() => instance;
static const String _key = 'memos';
List<Memo> _memos = [];
// 初期化
Future<void> initialize() async {
final prefs = await SharedPreferences.getInstance();
final jsonString = prefs.getString(_key);
if (jsonString != null) {
final jsonList = jsonDecode(jsonString) as List;
_memos = jsonList.map((json) => Memo.fromJson(json)).toList();
}
}
// メモ一覧取得
List<Memo> getMemos() {
return List.unmodifiable(_memos);
}
// メモ追加
Future<void> addMemo(String title, String content) async {
final memo = Memo(title: title, content: content);
_memos.insert(0, memo);
await _save();
}
// メモ更新
Future<void> updateMemo(String id, String title, String content) async {
final index = _memos.indexWhere((m) => m.id == id);
if (index != -1) {
_memos[index] = _memos[index].copyWith(
title: title,
content: content,
);
await _save();
}
}
// メモ削除
Future<void> deleteMemo(String id) async {
_memos.removeWhere((m) => m.id == id);
await _save();
}
// 保存
Future<void> _save() async {
final prefs = await SharedPreferences.getInstance();
final jsonList = _memos.map((m) => m.toJson()).toList();
await prefs.setString(_key, jsonEncode(jsonList));
}
}
Step 4: メイン画面作成
プロンプト:
メモ一覧画面を作成してください。
UI:
- AppBar: タイトル「メモ」と追加ボタン
- Body: メモ一覧(ListViewで表示)
- FloatingActionButton: 新規メモ作成
各メモアイテム:
- タイトルを太字で表示
- 内容を1行で表示(省略記号付き)
- 更新日時を小さく表示
- タップで編集画面へ
- スワイプで削除(確認ダイアログ表示)
状態管理:
- StatefulWidget
- MemoServiceを使用
Step 5: 編集画面作成
プロンプト:
メモ編集画面を作成してください。
UI:
- AppBar: タイトル「メモ編集」と保存ボタン
- タイトル入力フィールド
- 内容入力フィールド(複数行)
機能:
- 新規作成と編集の両方に対応
- 保存ボタンでMemoServiceを使って保存
- 保存後は前の画面に戻る
注意:
- TextEditingControllerを使用
- disposeで必ずController破棄
Step 6: 動作確認とリファクタリング
プロンプト:
このコードをレビューしてください:
1. エラーがないか
2. リソースリークはないか
3. より良い書き方があれば提案
4. Flutter 3.27のベストプラクティスに従っているか
簡潔に指摘してください。
完成イメージ
// main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await MemoService.instance.initialize();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'メモアプリ',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
useMaterial3: true,
),
home: const MemoListScreen(),
);
}
}
まとめ
このアプリの特徴:
シンプルさ: - ✅ Riverpod不使用 - ✅ 外部パッケージ最小限 - ✅ 理解しやすい構造
実用性: - ✅ データ永続化 - ✅ CRUD操作完備 - ✅ エラーハンドリング
保守性: - ✅ Serviceクラスで分離 - ✅ 自己完結型Widget - ✅ AIが理解しやすい
学んだパターン: 1. Serviceクラスパターン 2. StatefulWidgetでの状態管理 3. SharedPreferencesでの永続化 4. ナビゲーション 5. リソース管理
次のレシピでは、Google Antigravityについて(注意点付きで)解説します。
➡️ 次のレシピへ: 06_antigravity_with_cautions.md