flutter-bloc-development
Flutterで画面や部品、データ連携機能を開発する際に、BLoCという状態管理手法や整った構造、プロジェクトのデザインシステムを活用して、より効率的で高品質な実装を実現するSkill。
📜 元の英語説明(参考)
Build Flutter features using BLoC state management, clean architecture layers, and the project's design system. Apply when creating screens, widgets, or data integrations.
🇯🇵 日本人クリエイター向け解説
Flutterで画面や部品、データ連携機能を開発する際に、BLoCという状態管理手法や整った構造、プロジェクトのデザインシステムを活用して、より効率的で高品質な実装を実現するSkill。
※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。
下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o flutter-bloc-development.zip https://jpskill.com/download/8806.zip && unzip -o flutter-bloc-development.zip && rm flutter-bloc-development.zip
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/8806.zip -OutFile "$d\flutter-bloc-development.zip"; Expand-Archive "$d\flutter-bloc-development.zip" -DestinationPath $d -Force; ri "$d\flutter-bloc-development.zip"
完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。
💾 手動でダウンロードしたい(コマンドが難しい人向け)
- 1. 下の青いボタンを押して
flutter-bloc-development.zipをダウンロード - 2. ZIPファイルをダブルクリックで解凍 →
flutter-bloc-developmentフォルダができる - 3. そのフォルダを
C:\Users\あなたの名前\.claude\skills\(Win)または~/.claude/skills/(Mac)へ移動 - 4. Claude Code を再起動
⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。
🎯 このSkillでできること
下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。
📦 インストール方法 (3ステップ)
- 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
- 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
- 3. 展開してできたフォルダを、ホームフォルダの
.claude/skills/に置く- · macOS / Linux:
~/.claude/skills/ - · Windows:
%USERPROFILE%\.claude\skills\
- · macOS / Linux:
Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。
詳しい使い方ガイドを見る →- 最終更新
- 2026-05-18
- 取得日時
- 2026-05-18
- 同梱ファイル
- 1
📖 Skill本文(日本語訳)
※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。
Flutter BLoC 開発
このスキルでは、BLoC 状態管理、厳密なレイヤー分離、およびこのコードベースにおけるすべての Flutter 開発でのデザインシステム定数の必須使用を強制します。
意思決定ツリー: アプローチの選択
ユーザータスク → 何を構築しますか?
│
├─ 新しい画面/機能 → フルスタック実装:
│ 1. BLoC を定義する (events, states, bloc)
│ 2. データレイヤーを作成/更新する (repository, datasource)
│ 3. デザインシステムで UI を構築する
│
├─ 新しいウィジェットのみ → プレゼンテーションレイヤー:
│ 1. widgets/ (再利用可能) または screens/ (機能固有) に作成する
│ 2. デザインシステム定数を使用する (ハードコードされた値は使用しない)
│ 3. 必要に応じて既存の BLoC に接続する
│
├─ データ統合 → データレイヤーのみ:
│ 1. datasource を作成する (Supabase/Firebase SDK 呼び出し)
│ 2. repository を作成する (ドメインエンティティにマップする)
│ 3. 既存または新しい BLoC で配線する
│
└─ リファクタリング → 違反を特定する:
1. ハードコードされた色/間隔/タイポグラフィを確認する
2. UI にビジネスロジックがないか確認する
3. datasource の外部で直接 SDK 呼び出しがないか確認する
4. 非同期操作の前に Loading 状態が欠落していないか確認する
5. Events/States で Equatable が欠落していないか確認する
6. 不適切なエラー処理がないか確認する (SnackBar + AppColors.error を使用)
アーキテクチャの概要
lib/
├── bloc/[feature]/ # Events, States, BLoC
├── data/datasources/ # バックエンド SDK 呼び出し (Supabase, Firebase)
├── data/repositories/ # データオーケストレーション、エンティティにマップ
├── data/models/ # DTO、JSON シリアライゼーション
├── domain/entities/ # 純粋な Dart ビジネスオブジェクト
├── screens/ # 機能画面
├── widgets/ # 再利用可能なコンポーネント
└── utils/ # デザインシステム (colors, spacing, typography)
重要なルール:
- すべての状態変更は BLoC を介して流れる
- datasource の外部で直接バックエンド SDK 呼び出しを行わない
- ハードコードされた値 (colors, spacing, typography) はゼロ
- すべてのデータアクセスにリポジトリパターンを使用する
BLoC 実装
Event → State → BLoC (機能ごとに 3 つのファイル)
Events — ユーザーアクションとシステムトリガー:
abstract class FeatureEvent extends Equatable {
const FeatureEvent();
@override
List<Object?> get props => [];
}
class FeatureActionRequested extends FeatureEvent {
final String param;
const FeatureActionRequested({required this.param});
@override
List<Object> get props => [param];
}
States — 考えられるすべての UI 状態:
abstract class FeatureState extends Equatable {
const FeatureState();
@override
List<Object?> get props => [];
}
class FeatureInitial extends FeatureState {}
class FeatureLoading extends FeatureState {}
class FeatureSuccess extends FeatureState {
final DataType data;
const FeatureSuccess(this.data);
@override
List<Object> get props => [data];
}
class FeatureError extends FeatureState {
final String message;
const FeatureError(this.message);
@override
List<Object> get props => [message];
}
BLoC — Loading → Success/Error パターンのイベントハンドラー:
class FeatureBloc extends Bloc<FeatureEvent, FeatureState> {
final FeatureRepository _repository;
FeatureBloc({required FeatureRepository repository})
: _repository = repository,
super(FeatureInitial()) {
on<FeatureActionRequested>(_onActionRequested);
}
Future<void> _onActionRequested(
FeatureActionRequested event,
Emitter<FeatureState> emit,
) async {
emit(FeatureLoading());
try {
final result = await _repository.doSomething(event.param);
emit(FeatureSuccess(result));
} catch (e) {
emit(FeatureError(e.toString()));
}
}
}
重要: 非同期処理の前に必ず Loading を発行し、次に Success または Error を発行します。ローディング状態をスキップしないでください。
データレイヤー
データフロー:
UI Event → BLoC (emit Loading) → Repository → Datasource (SDK)
↓
Response → Repository (map to entity) → BLoC (emit Success/Error) → UI
Datasource — バックエンド SDK 呼び出しのみ:
class FeatureDataSource {
final SupabaseClient _supabase;
FeatureDataSource(this._supabase);
Future<Map<String, dynamic>> fetch() async {
return await _supabase.from('table').select().single();
}
}
Repository — オーケストレーションとマッピング:
class FeatureRepository {
final FeatureDataSource _dataSource;
FeatureRepository(this._dataSource);
Future<DomainEntity> fetchData() async {
final response = await _dataSource.fetch();
return DomainEntity.fromJson(response);
}
}
デザインシステム (交渉の余地なし)
Colors
✅ AppColors.primary、AppColors.error、AppColors.textPrimary
❌ Color(0xFF...)、Colors.blue、インライン 16 進数値
Spacing
✅ AppSpacing.xs (4)、AppSpacing.sm (8)、AppSpacing.md (16)、AppSpacing.lg (24)、AppSpacing.xl (32)
✅ AppSpacing.screenHorizontal (24)、AppSpacing.screenVertical (16)
❌ EdgeInsets.all(16.0)、ハードコードされたパディング値
Border Radius
✅ AppRadius.sm (8)、AppRadius.md (12)、AppRadius.lg (16)、AppRadius.xl (24)
❌ BorderRadius.circular(12)、インライン半径値
Typography
✅ AppTypography.headlineLarge、AppTypography.bodyMedium、theme.textTheme.bodyMedium
❌ TextStyle(fontSize: 16)、インラインテキストスタイル
UI パターン
画面テンプレート
GradientScaffold(
body: SafeArea(
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(AppSpacing.screenHorizontal),
child: HeaderWidget(),
),
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.screenHorizontal),
child: ContentWidget(),
),
),
Padding(
padding: const EdgeInsets.all(AppSpacing.screenHorizontal),
child: ActionButton(
onPressed: () => context.read<FeatureBloc>().add(ActionEvent()),
),
)
(原文がここで切り詰められています) 📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開
Flutter BLoC Development
This skill enforces BLoC state management, strict layer separation, and mandatory use of design system constants for all Flutter development in this codebase.
Decision Tree: Choosing Your Approach
User task → What are they building?
│
├─ New screen/feature → Full stack implementation:
│ 1. Define BLoC (events, states, bloc)
│ 2. Create/update data layer (repository, datasource)
│ 3. Build UI with design system
│
├─ New widget only → Presentation layer:
│ 1. Create in widgets/ (reusable) or screens/ (feature-specific)
│ 2. Use design system constants (NO hardcoded values)
│ 3. Connect to existing BLoC if needed
│
├─ Data integration → Data layer only:
│ 1. Create datasource (Supabase/Firebase SDK calls)
│ 2. Create repository (maps to domain entities)
│ 3. Wire up in existing or new BLoC
│
└─ Refactoring → Identify violations:
1. Check for hardcoded colors/spacing/typography
2. Check for business logic in UI
3. Check for direct SDK calls outside datasources
4. Check for missing Loading state before async operations
5. Check for missing Equatable on Events/States
6. Check for improper error handling (use SnackBar + AppColors.error)
Architecture at a Glance
lib/
├── bloc/[feature]/ # Events, States, BLoC
├── data/datasources/ # Backend SDK calls (Supabase, Firebase)
├── data/repositories/ # Data orchestration, maps to entities
├── data/models/ # DTOs, JSON serialization
├── domain/entities/ # Pure Dart business objects
├── screens/ # Feature screens
├── widgets/ # Reusable components
└── utils/ # Design system (colors, spacing, typography)
Key Rules:
- All state changes flow through BLoC
- No direct backend SDK calls outside datasources
- Zero hardcoded values (colors, spacing, typography)
- Repository pattern for all data access
BLoC Implementation
Event → State → BLoC (Three Files Per Feature)
Events — User actions and system triggers:
abstract class FeatureEvent extends Equatable {
const FeatureEvent();
@override
List<Object?> get props => [];
}
class FeatureActionRequested extends FeatureEvent {
final String param;
const FeatureActionRequested({required this.param});
@override
List<Object> get props => [param];
}
States — All possible UI states:
abstract class FeatureState extends Equatable {
const FeatureState();
@override
List<Object?> get props => [];
}
class FeatureInitial extends FeatureState {}
class FeatureLoading extends FeatureState {}
class FeatureSuccess extends FeatureState {
final DataType data;
const FeatureSuccess(this.data);
@override
List<Object> get props => [data];
}
class FeatureError extends FeatureState {
final String message;
const FeatureError(this.message);
@override
List<Object> get props => [message];
}
BLoC — Event handlers with Loading → Success/Error pattern:
class FeatureBloc extends Bloc<FeatureEvent, FeatureState> {
final FeatureRepository _repository;
FeatureBloc({required FeatureRepository repository})
: _repository = repository,
super(FeatureInitial()) {
on<FeatureActionRequested>(_onActionRequested);
}
Future<void> _onActionRequested(
FeatureActionRequested event,
Emitter<FeatureState> emit,
) async {
emit(FeatureLoading());
try {
final result = await _repository.doSomething(event.param);
emit(FeatureSuccess(result));
} catch (e) {
emit(FeatureError(e.toString()));
}
}
}
CRITICAL: Always emit Loading before async work, then Success or Error. Never skip the loading state.
Data Layer
Data Flow:
UI Event → BLoC (emit Loading) → Repository → Datasource (SDK)
↓
Response → Repository (map to entity) → BLoC (emit Success/Error) → UI
Datasource — Backend SDK calls only:
class FeatureDataSource {
final SupabaseClient _supabase;
FeatureDataSource(this._supabase);
Future<Map<String, dynamic>> fetch() async {
return await _supabase.from('table').select().single();
}
}
Repository — Orchestration and mapping:
class FeatureRepository {
final FeatureDataSource _dataSource;
FeatureRepository(this._dataSource);
Future<DomainEntity> fetchData() async {
final response = await _dataSource.fetch();
return DomainEntity.fromJson(response);
}
}
Design System (Non-Negotiable)
Colors
✅ AppColors.primary, AppColors.error, AppColors.textPrimary
❌ Color(0xFF...), Colors.blue, inline hex values
Spacing
✅ AppSpacing.xs (4), AppSpacing.sm (8), AppSpacing.md (16), AppSpacing.lg (24), AppSpacing.xl (32)
✅ AppSpacing.screenHorizontal (24), AppSpacing.screenVertical (16)
❌ EdgeInsets.all(16.0), hardcoded padding values
Border Radius
✅ AppRadius.sm (8), AppRadius.md (12), AppRadius.lg (16), AppRadius.xl (24)
❌ BorderRadius.circular(12), inline radius values
Typography
✅ AppTypography.headlineLarge, AppTypography.bodyMedium, theme.textTheme.bodyMedium
❌ TextStyle(fontSize: 16), inline text styles
UI Patterns
Screen Template
GradientScaffold(
body: SafeArea(
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(AppSpacing.screenHorizontal),
child: HeaderWidget(),
),
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.screenHorizontal),
child: ContentWidget(),
),
),
Padding(
padding: const EdgeInsets.all(AppSpacing.screenHorizontal),
child: ActionButton(
onPressed: () => context.read<FeatureBloc>().add(ActionEvent()),
),
),
],
),
),
)
BLoC Consumer Pattern
BlocConsumer<FeatureBloc, FeatureState>(
listener: (context, state) {
if (state is FeatureError) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(state.message), backgroundColor: AppColors.error),
);
}
},
builder: (context, state) {
if (state is FeatureLoading) return const Center(child: CircularProgressIndicator());
if (state is FeatureSuccess) return SuccessWidget(data: state.data);
return const SizedBox.shrink();
},
)
Common Pitfalls
❌ Business logic in widgets → Move to BLoC
❌ Direct Supabase/Firebase calls in repository → Move to datasource
❌ Skipping loading state before async operations → Always emit Loading first
❌ Hardcoded colors like Color(0xFF4A90A4) → Use AppColors.primary
❌ Magic numbers like padding: 16 → Use AppSpacing.md
Quick Reference
| Action | Pattern |
|---|---|
| Dispatch event | context.read<Bloc>().add(Event()) |
| Watch state inline | context.watch<Bloc>().state |
| Listen + Build | BlocConsumer |
| Listen only | BlocListener |
| Build only | BlocBuilder |
Checklist Before Submitting
- [ ] Events/States/BLoC use
Equatable - [ ] All async: Loading → Success/Error
- [ ] No business logic in UI
- [ ] No SDK calls outside datasources
- [ ] Zero hardcoded colors/spacing/typography
- [ ] Error handling shows SnackBar with
AppColors.error - [ ] Code formatted with
dart format