Last active
June 9, 2025 04:40
-
-
Save memory-lovers/eb784a20abf8667aea312466699d3400 to your computer and use it in GitHub Desktop.
Claude Opus 4で生成してもらったCLAUD.mdのサンプル(Flutter+Melos+FVM)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# CLAUD.md - Flutter Monorepo Project Guide | |
## プロジェクト概要 | |
このプロジェクトはFlutter + Melos + FVMを使用したモノレポ構成のモバイルアプリケーションです。Claudeに対する包括的なガイドラインを提供し、一貫性のある高品質なコード生成を実現します。 | |
## 技術スタック | |
- **Flutter**: 3.19.0 (FVMで管理) | |
- **Dart**: 3.3.0 | |
- **Melos**: 3.4.0 | |
- **状態管理**: Riverpod 2.5.0 | |
- **ルーティング**: go_router 13.2.0 | |
- **DI**: get_it + injectable | |
- **ローカルDB**: drift | |
- **API通信**: dio + retrofit | |
- **テスト**: flutter_test, mockito, golden_toolkit | |
## プロジェクト構造 | |
``` | |
project_root/ | |
├── melos.yaml | |
├── .fvm/ | |
│ └── fvm_config.json | |
├── apps/ | |
│ ├── mobile_app/ # メインアプリ | |
│ └── admin_app/ # 管理画面アプリ | |
├── packages/ | |
│ ├── core/ # 共通コア機能 | |
│ ├── ui_kit/ # UIコンポーネントライブラリ | |
│ ├── api_client/ # API通信層 | |
│ ├── domain/ # ドメインモデル | |
│ └── infrastructure/ # インフラ層 | |
└── tools/ # 開発ツール | |
``` | |
## コーディング規約 | |
### 命名規則 | |
```dart | |
// ファイル名: snake_case | |
user_profile_screen.dart | |
// クラス名: PascalCase | |
class UserProfileScreen extends ConsumerWidget {} | |
// 変数・関数: camelCase | |
final userRepository = ref.watch(userRepositoryProvider); | |
void updateUserProfile() {} | |
// 定数: lowerCamelCase (constプレフィックス不要) | |
const defaultTimeout = Duration(seconds: 30); | |
// Provider: camelCase + Provider suffix | |
final userRepositoryProvider = Provider<UserRepository>((ref) => ...); | |
``` | |
### ディレクトリ構造(各パッケージ内) | |
``` | |
lib/ | |
├── src/ | |
│ ├── presentation/ # UI層 | |
│ │ ├── screens/ | |
│ │ ├── widgets/ | |
│ │ └── providers/ | |
│ ├── application/ # アプリケーションロジック | |
│ │ ├── use_cases/ | |
│ │ └── services/ | |
│ ├── domain/ # ドメイン層 | |
│ │ ├── models/ | |
│ │ ├── repositories/ | |
│ │ └── value_objects/ | |
│ └── infrastructure/ # インフラ層 | |
│ ├── datasources/ | |
│ ├── repositories/ | |
│ └── dtos/ | |
└── package_name.dart # パブリックAPI | |
``` | |
## Riverpod規約 | |
### Provider定義 | |
```dart | |
// 常にファイル最上部に配置 | |
// part指定の直後 | |
part 'user_screen.g.dart'; | |
// 1. 単純なProvider | |
@riverpod | |
UserRepository userRepository(UserRepositoryRef ref) { | |
return UserRepository(ref.watch(dioProvider)); | |
} | |
// 2. FutureProvider (自動的にAsyncValue<T>を返す) | |
@riverpod | |
Future<User> currentUser(CurrentUserRef ref) async { | |
final repo = ref.watch(userRepositoryProvider); | |
return repo.getCurrentUser(); | |
} | |
// 3. StreamProvider | |
@riverpod | |
Stream<List<Message>> messages(MessagesRef ref) { | |
return ref.watch(chatRepositoryProvider).messagesStream(); | |
} | |
// 4. NotifierProvider (状態管理) | |
@riverpod | |
class UserProfile extends _$UserProfile { | |
@override | |
User? build() => null; | |
Future<void> update(User user) async { | |
state = await ref.read(userRepositoryProvider).updateUser(user); | |
} | |
} | |
``` | |
### Widget内での使用 | |
```dart | |
class UserScreen extends ConsumerWidget { | |
@override | |
Widget build(BuildContext context, WidgetRef ref) { | |
// 監視(リビルドあり) | |
final user = ref.watch(currentUserProvider); | |
// 読み取り(リビルドなし) | |
final onTap = () => ref.read(userProfileProvider.notifier).update(user); | |
return user.when( | |
data: (user) => UserProfile(user: user), | |
loading: () => const CircularProgressIndicator(), | |
error: (err, stack) => ErrorWidget(err), | |
); | |
} | |
} | |
``` | |
## エラーハンドリング | |
### API通信 | |
```dart | |
@riverpod | |
class UserRepository extends _$UserRepository { | |
Future<User> getUser(String id) async { | |
try { | |
final response = await ref.read(dioProvider).get('/users/$id'); | |
return User.fromJson(response.data); | |
} on DioException catch (e) { | |
throw switch (e.response?.statusCode) { | |
404 => UserNotFoundException(id), | |
401 => UnauthorizedException(), | |
_ => NetworkException(e.message ?? 'Unknown error'), | |
}; | |
} | |
} | |
} | |
``` | |
### UI層でのエラー表示 | |
```dart | |
Widget build(BuildContext context, WidgetRef ref) { | |
final userAsync = ref.watch(currentUserProvider); | |
return userAsync.when( | |
data: (user) => UserContent(user: user), | |
loading: () => const LoadingIndicator(), | |
error: (error, stack) => ErrorView( | |
message: switch (error) { | |
UserNotFoundException() => 'ユーザーが見つかりません', | |
UnauthorizedException() => 'ログインが必要です', | |
NetworkException() => 'ネットワークエラーが発生しました', | |
_ => '予期しないエラーが発生しました', | |
}, | |
onRetry: () => ref.invalidate(currentUserProvider), | |
), | |
); | |
} | |
``` | |
## テスト規約 | |
### 単体テスト | |
```dart | |
@GenerateNiceMocks([MockSpec<UserRepository>()]) | |
void main() { | |
group('UserProfile Notifier', () { | |
late ProviderContainer container; | |
late MockUserRepository mockRepo; | |
setUp(() { | |
mockRepo = MockUserRepository(); | |
container = ProviderContainer( | |
overrides: [ | |
userRepositoryProvider.overrideWithValue(mockRepo), | |
], | |
); | |
}); | |
test('should update user profile', () async { | |
final user = User(id: '1', name: 'Test'); | |
when(mockRepo.updateUser(any)).thenAnswer((_) async => user); | |
await container.read(userProfileProvider.notifier).update(user); | |
expect(container.read(userProfileProvider), equals(user)); | |
}); | |
}); | |
} | |
``` | |
### Widgetテスト | |
```dart | |
testWidgets('shows loading indicator', (tester) async { | |
await tester.pumpWidget( | |
ProviderScope( | |
overrides: [ | |
currentUserProvider.overrideWith((ref) async { | |
await Future.delayed(const Duration(seconds: 1)); | |
return User(id: '1', name: 'Test'); | |
}), | |
], | |
child: const MaterialApp(home: UserScreen()), | |
), | |
); | |
expect(find.byType(CircularProgressIndicator), findsOneWidget); | |
}); | |
``` | |
## Melos Scripts | |
```yaml | |
# melos.yaml | |
name: my_project | |
packages: | |
- apps/** | |
- packages/** | |
scripts: | |
# 開発用 | |
get: | |
exec: flutter pub get | |
description: すべてのパッケージで依存関係を取得 | |
analyze: | |
exec: flutter analyze | |
description: 静的解析を実行 | |
test: | |
exec: flutter test | |
description: テストを実行 | |
packageFilters: | |
dirExists: test | |
# コード生成 | |
generate: | |
exec: dart run build_runner build --delete-conflicting-outputs | |
description: コード生成を実行 | |
packageFilters: | |
dependsOn: build_runner | |
# リリース用 | |
build:apk: | |
run: | | |
cd apps/mobile_app && \ | |
flutter build apk --release | |
description: APKをビルド | |
``` | |
## パフォーマンス最適化 | |
### 1. Widget最適化 | |
```dart | |
// ❌ 避けるべき: 不必要な再ビルド | |
class BadExample extends ConsumerWidget { | |
Widget build(context, ref) { | |
final allData = ref.watch(heavyDataProvider); | |
return Column( | |
children: [ | |
Text(allData.title), // titleだけ使用 | |
], | |
); | |
} | |
} | |
// ✅ 推奨: 必要なデータのみ監視 | |
class GoodExample extends ConsumerWidget { | |
Widget build(context, ref) { | |
final title = ref.watch(heavyDataProvider.select((data) => data.title)); | |
return Column( | |
children: [ | |
Text(title), | |
], | |
); | |
} | |
} | |
``` | |
### 2. Provider最適化 | |
```dart | |
// ✅ 自動破棄を活用 | |
@riverpod | |
Future<List<Product>> products(ProductsRef ref, {required String category}) async { | |
// keepAlive: falseがデフォルト - 使用されなくなったら自動破棄 | |
final repo = ref.watch(productRepositoryProvider); | |
return repo.getProducts(category); | |
} | |
// ✅ 必要な場合のみkeepAlive | |
@Riverpod(keepAlive: true) | |
AppConfig appConfig(AppConfigRef ref) { | |
// アプリ全体で使用する設定は保持 | |
return AppConfig.load(); | |
} | |
``` | |
### 3. 画像最適化 | |
```dart | |
// ✅ 適切なキャッシュ設定 | |
CachedNetworkImage( | |
imageUrl: url, | |
cacheKey: 'user_avatar_${user.id}', | |
maxHeightDiskCache: 200, | |
maxWidthDiskCache: 200, | |
memCacheHeight: 100, | |
memCacheWidth: 100, | |
) | |
``` | |
## セキュリティ考慮事項 | |
### 1. 認証トークン管理 | |
```dart | |
// ✅ SecureStorageを使用 | |
@Riverpod(keepAlive: true) | |
class AuthToken extends _$AuthToken { | |
@override | |
Future<String?> build() async { | |
return await ref.read(secureStorageProvider).read(key: 'auth_token'); | |
} | |
Future<void> save(String token) async { | |
await ref.read(secureStorageProvider).write(key: 'auth_token', value: token); | |
ref.invalidateSelf(); | |
} | |
} | |
``` | |
### 2. API通信 | |
```dart | |
// ✅ Interceptorで自動的にトークンを付与 | |
class AuthInterceptor extends Interceptor { | |
final Ref ref; | |
@override | |
void onRequest(options, handler) { | |
final token = ref.read(authTokenProvider).valueOrNull; | |
if (token != null) { | |
options.headers['Authorization'] = 'Bearer $token'; | |
} | |
handler.next(options); | |
} | |
} | |
``` | |
## よくあるパターン | |
### 1. 無限スクロール | |
```dart | |
@riverpod | |
class PostList extends _$PostList { | |
int _page = 0; | |
bool _hasMore = true; | |
@override | |
Future<List<Post>> build() async { | |
return _loadPage(0); | |
} | |
Future<void> loadMore() async { | |
if (!_hasMore || state.isLoading) return; | |
state = const AsyncValue.loading(); | |
state = await AsyncValue.guard(() async { | |
final current = state.valueOrNull ?? []; | |
final newPosts = await _loadPage(_page + 1); | |
_page++; | |
_hasMore = newPosts.length == 20; // ページサイズが20の場合 | |
return [...current, ...newPosts]; | |
}); | |
} | |
Future<List<Post>> _loadPage(int page) async { | |
final repo = ref.read(postRepositoryProvider); | |
return repo.getPosts(page: page, limit: 20); | |
} | |
} | |
``` | |
### 2. フォーム管理 | |
```dart | |
@freezed | |
class ProfileForm with _$ProfileForm { | |
const factory ProfileForm({ | |
@Default('') String name, | |
@Default('') String email, | |
@Default(false) bool isSubmitting, | |
String? errorMessage, | |
}) = _ProfileForm; | |
} | |
@riverpod | |
class ProfileFormController extends _$ProfileFormController { | |
@override | |
ProfileForm build() => const ProfileForm(); | |
void updateName(String name) { | |
state = state.copyWith(name: name); | |
} | |
void updateEmail(String email) { | |
state = state.copyWith(email: email); | |
} | |
Future<void> submit() async { | |
if (state.isSubmitting) return; | |
state = state.copyWith(isSubmitting: true, errorMessage: null); | |
try { | |
await ref.read(userRepositoryProvider).updateProfile( | |
name: state.name, | |
email: state.email, | |
); | |
// 成功処理 | |
} catch (e) { | |
state = state.copyWith( | |
isSubmitting: false, | |
errorMessage: e.toString(), | |
); | |
} | |
} | |
} | |
``` | |
## Claudeへの指示 | |
### コード生成時の規則 | |
1. **必ずriverpod_generatorを使用** - 手動でProviderを書かない | |
2. **freezedを積極的に活用** - データクラスは全てfreezedで定義 | |
3. **明示的な型定義** - 型推論に頼らず明示的に型を指定 | |
4. **エラーハンドリング必須** - try-catchまたはAsyncValue.guardを使用 | |
5. **日本語コメント推奨** - ビジネスロジックは日本語で説明 | |
### 回答形式 | |
1. **段階的な実装** - 複雑な機能は段階的に説明 | |
2. **完全なコード例** - importから含めた完全なコード | |
3. **テストコード付き** - 実装と同時にテストコードも提供 | |
4. **パフォーマンス考慮** - 最適化ポイントを明示 | |
### 避けるべきこと | |
- StatefulWidgetの使用(ConsumerWidget/ConsumerStatefulWidgetを使用) | |
- context.read/watch(ref.read/watchを使用) | |
- 手動でのProviderScope管理(必要最小限に) | |
- グローバル変数(Providerで管理) | |
- 非同期処理でのsetState(Riverpodで管理) | |
## 開発フロー | |
1. **機能追加時** | |
```bash | |
# 新しいパッケージ作成 | |
melos exec -- flutter create --template=package packages/new_feature | |
# 依存関係追加 | |
cd packages/new_feature | |
flutter pub add freezed_annotation riverpod_annotation | |
flutter pub add --dev build_runner freezed riverpod_generator | |
# コード生成 | |
melos run generate | |
``` | |
2. **テスト実行** | |
```bash | |
# 全体テスト | |
melos run test | |
# 特定パッケージのみ | |
melos exec --scope="*api_client*" -- flutter test | |
``` | |
3. **リリースビルド** | |
```bash | |
# バージョン更新 | |
melos version | |
# ビルド | |
melos run build:apk | |
melos run build:ios | |
``` | |
## トラブルシューティング | |
### よくある問題と解決法 | |
1. **コード生成エラー** | |
```bash | |
# キャッシュクリア | |
melos clean | |
melos bootstrap | |
melos run generate | |
``` | |
2. **Provider not found エラー** | |
- ProviderScopeでラップされているか確認 | |
- overrideが正しく設定されているか確認 | |
3. **パフォーマンス問題** | |
- DevToolsでWidget rebuildsを確認 | |
- 不要なref.watchを削除 | |
- selectを使用して必要なデータのみ監視 | |
## 参考リンク | |
- [Riverpod Documentation](https://riverpod.dev/) | |
- [Melos Documentation](https://melos.invertase.dev/) | |
- [Flutter Performance Best Practices](https://docs.flutter.dev/perf/best-practices) | |
- [当プロジェクトのDesign System](packages/ui_kit/README.md) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Flutter + Melos + FVMプロジェクト用の包括的なCLAUD.mdを作成します:
このCLAUD.mdは、Flutter + Melos + FVMプロジェクトで最高のパフォーマンスを引き出すために設計されています。主な特徴:
このCLAUD.mdをプロジェクトルートに配置することで、Claudeは:
必要に応じて、プロジェクトの具体的な要件に合わせてカスタマイズしてください。