flutter-dev について
Flutter チームの実践に沿って、Flutter と Dart の開発を支援するスキルです。コード構成、状態管理、レイアウト、ナビゲーション、テスト、アクセシビリティを扱います。
主な機能
- 01SOLID と Dart のスタイル規約
- 02レイヤー別または機能別の構成
- 03組み込み状態管理の選択指針
- 04レスポンシブレイアウトとナビゲーション
- 05テストとコード解析の推奨事項
- 06アクセシビリティとテーマ設計
活用シーン
- 01Flutter プロジェクト構成の計画
- 02レスポンシブ UI とナビゲーションの実装
- 03状態管理と非同期処理の選択
- 04アプリのテストとコード解析
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ bogdanustyak/ flutter-expert-skill --skill flutter-devインストール後、Claude Code で/flutter-devを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
README.mdreferences/navigation_patterns.mdtheme_patterns.mdstate_patterns.md詳細
Flutter 開発エキスパート
Flutter チームの公式ベストプラクティスに沿った、Flutter と Dart 開発の専門的なガイダンスです。
開発の基本原則
コードの理念
- コードベース全体に SOLID 原則を適用します
- 関数型および宣言型のパターンを取り入れた、簡潔でモダンかつ技術的な Dart コードを書きます
- 複雑なウィジェットやロジックを構築する際は、継承よりもコンポジションを優先します
- 特にウィジェットには不変のデータ構造を優先します(可能な限り
StatelessWidgetを使用します) - 適切な状態管理を使用して、一時的な状態とアプリの状態を分離します
- 関数は単一の目的を持つ短いものにします(20 行未満を目指します)
- 意味が明確で説明的な名前を付け、省略形は避けます
プロジェクト構造の前提
- エントリーポイントを
lib/main.dartとする標準的な Flutter プロジェクト構造 - 論理的なレイヤーごとに整理します:プレゼンテーション(ウィジェット/画面)、ドメイン(ビジネスロジック)、データ(モデル/API クライアント)、コア(ユーティリティ/拡張機能)
- 大規模なプロジェクトでは、機能ごとに整理し、各機能にプレゼンテーション/ドメイン/データのサブフォルダーを設けます
対話のガイドライン
コードを生成する際:
- null safety、futures、streams など、Dart 固有の機能について説明します
- 依頼が曖昧な場合は、機能と対象プラットフォームについて確認します
- pub.dev の新しい依存関係を提案する場合は、その利点を説明します
- 一貫したフォーマットを適用するために
dart formatツールを使用します - よくあるエラーを自動修正するために
dart fixツールを使用します - 問題を検出するために、推奨ルールを適用した Dart リンターを使用します
コード品質基準
スタイルのルール
- 行の長さ:80 文字以下
- 命名規則:
- クラスには
PascalCase - メンバー/変数/関数/enum には
camelCase - ファイルには
snake_case
- クラスには
- 行末コメントは使用しません
- シンプルな 1 行関数にはアロー構文を使用します
エラー処理
- 潜在的なエラーを予測して処理し、決して黙って失敗しないようにします
- 適切な例外型を使った
try-catchブロックを使用します - コード固有の状況にはカスタム例外を使用します
- 堅牢なエラー処理を伴う、適切な
async/awaitの使用
ドキュメント
- すべての公開 API(クラス、コンストラクター、メソッド、トップレベル関数)に
dartdocコメントを追加します - 複雑なコードや自明でないコードには、わかりやすいコメントを書きます
- ドキュメントコメントには
///を使用します - 句点で終わる 1 文の要約から始めます
- コードが何をするかではなく、なぜそのように書かれているかをコメントします
Dart のベストプラクティス
型システムと null safety
- sound null safety に準拠したコードを書きます
- Dart の null safety 機能を活用します
- 値が null でないことが保証されない限り、
!演算子を避けます - コードを簡潔にできる場合は、パターンマッチング機能を使用します
- クラスを定義するのが煩雑な場合は、複数の型を返すためにレコードを使用します
- 網羅的な
switch文/式を優先します(breakは不要です)
非同期プログラミング
- 単一の非同期処理には
Future、async、awaitを使用します - 非同期イベントのシーケンスには
Streamを使用します - 非同期処理で適切にエラーを処理します
クラスとライブラリの整理
- 関連するクラスは同じライブラリファイル内に定義します
- 大規模なライブラリでは、単一のトップレベルライブラリから小さなプライベートライブラリをエクスポートします
- 関連するライブラリは同じフォルダーにまとめます
Flutter のベストプラクティス
ウィジェットの設計
- ウィジェット(特に
StatelessWidget)は不変です - UI を変更する必要がある場合、Flutter はウィジェットツリーを再構築します
- 既存のウィジェットを拡張するより、小さなウィジェットを組み合わせることを優先します
- ウィジェットを返すプライベートなヘルパーメソッドではなく、小さなプライベート
Widgetクラスを使用します - 大きな
build()メソッドは、再利用可能な小さなプライベート Widget クラスに分割します - 再構築を減らすため、可能な限り
constコンストラクターを使用します
パフォーマンスの最適化
- 長いリストには
ListView.builderまたはSliverListを使用します(遅延読み込み) - コストの高い計算(JSON の解析など)を別の isolate で実行するには、
compute()を使用します build()メソッド内で、コストの高い処理(ネットワーク呼び出し、複雑な計算)を避けます- 再構築を最小限に抑えるため、
build()メソッド内でconstコンストラクターを使用します
レスポンシブデザイン
- レスポンシブな UI には
LayoutBuilderまたはMediaQueryを使用します - さまざまな画面サイズに対応したモバイル向けデザインにします
- モバイルと Web の両方のプラットフォームでテストします
状態管理
サードパーティ製のソリューションを明示的に求められていない限り、Flutter 組み込みのソリューションを優先します。
組み込みソリューション(シンプルな順)
- ValueNotifier + ValueListenableBuilder - 単一の値を扱うシンプルなローカル状態
final ValueNotifier<int> _counter = ValueNotifier<int>(0);
ValueListenableBuilder<int>(
valueListenable: _counter,
builder: (context, value, child) => Text('Count: $value'),
);
- ChangeNotifier + ListenableBuilder - 複数のウィジェット間で共有する複雑な状態
class CounterModel extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
ListenableBuilder(
listenable: counterModel,
builder: (context, child) => Text('${counterModel.count}'),
);
- Streams + StreamBuilder - 非同期イベントのシーケンス
- Futures + FutureBuilder - 単一の非同期処理
高度なパターン
- MVVM:堅牢なアプリケーション向けの Model-View-ViewModel パターン
- 依存性注入:依存関係を明示するため、手動でコンストラクター注入を使用します
- Provider:手動注入を超える DI を明示的に求められた場合にのみ使用します
ナビゲーション
GoRouter(推奨)
宣言的なナビゲーション、ディープリンク、Web サポートには go_router を使用します。
// Add dependency
flutter pub add go_router
// Configure router
final GoRouter _router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomeScreen(),
routes: [
GoRoute(
path: 'details/:id',
builder: (context, state) {
final String id = state.pathParameters['id']!;
return DetailScreen(id: id);
},
),
],
),
],
);
// Use in MaterialApp
MaterialApp.router(routerConfig: _router);
- 認証フローには
redirectプロパティを設定します - ディープリンク可能なルートに使用します
Navigator(組み込み)
ディープリンクが不要な短時間表示の画面(ダイアログ、一時的なビュー)に使用します。
Navigator.push(context, MaterialPageRoute(builder: (context) => DetailsScreen()));
Navigator.pop(context);
パッケージ管理
pub ツールの使用
- 依存関係を追加:
flutter pub add <package_name> - 開発用依存関係を追加:
flutter pub add dev:<package_name> - オーバーライドを追加:
flutter pub add override:<package_name>:1.0.0 - 依存関係を削除:
dart pub remove <package_name>
外部パッケージ
- pub.dev で適切かつ安定したパッケージを検索します
- 新しい依存関係を提案する際は、その利点を説明します
データ処理
JSON シリアライズ
json_serializable と json_annotation を使用します。
import 'package:json_annotation/json_annotation.dart';
part 'user.g.dart';
@JsonSerializable(fieldRename: FieldRename.snake)
class User {
final String firstName;
final String lastName;
User({required this.firstName, required this.lastName});
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
Map<String, dynamic> toJson() => _$UserToJson(this);
}
コード生成
build_runnerが開発用依存関係に含まれていることを確認します- 変更後に実行します:
dart run build_runner build --delete-conflicting-outputs
ロギング
構造化ロギングには dart:developer を使用します。
import 'dart:developer' as developer;
// Simple messages
developer.log('User logged in successfully.');
// Structured error logging
try {
// code
} catch (e, s) {
developer.log(
'Failed to fetch data',
name: 'myapp.network',
level: 1000, // SEVERE
error: e,
stackTrace: s,
);
}
UI とテーマ
Material Design 3 と ThemeData
調和のとれたカラーパレットには ColorScheme.fromSeed() を使用します。
MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.deepPurple,
brightness: Brightness.light,
),
textTheme: TextTheme(
displayLarge: TextStyle(fontSize: 57, fontWeight: FontWeight.bold),
bodyMedium: TextStyle(fontSize: 14, height: 1.4),
),
),
darkTheme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.deepPurple,
brightness: Brightness.dark,
),
),
themeMode: ThemeMode.system,
);
カスタムテーマ拡張
標準の ThemeData を超えるカスタムデザイントークンには、次を使用します。
@immutable
class MyColors extends ThemeExtension<MyColors> {
const MyColors({required this.success, required this.danger});
final Color? success;
final Color? danger;
@override
ThemeExtension<MyColors> copyWith({Color? success, Color? danger}) {
return MyColors(success: success ?? this.success, danger: danger ?? this.danger);
}
@override
ThemeExtension<MyColors> lerp(ThemeExtension<MyColors>? other, double t) {
if (other is! MyColors) return this;
return MyColors(
success: Color.lerp(success, other.success, t),
danger: Color.lerp(danger, other.danger, t),
);
}
}
// Register in ThemeData
theme: ThemeData(
extensions: [MyColors(success: Colors.green, danger: Colors.red)],
),
// Use in widgets
Container(color: Theme.of(context).extension<MyColors>()!.success)
フォント
カスタムフォントには google_fonts パッケージを使用します。
flutter pub add google_fonts
final TextTheme appTextTheme = TextTheme(
displayLarge: GoogleFonts.oswald(fontSize: 57, fontWeight: FontWeight.bold),
titleLarge: GoogleFonts.roboto(fontSize: 22, fontWeight: FontWeight.w500),
bodyMedium: GoogleFonts.openSans(fontSize: 14),
);
画像とアセット
pubspec.yaml に宣言します。
flutter:
uses-material-design: true
assets:
- assets/images/
// Local images
Image.asset('assets/images/placeholder.png')
// Network images (always include error handling)
Image.network(
'https://example.com/image.png',
loadingBuilder: (context, child, progress) {
if (progress == null) return child;
return Center(child: CircularProgressIndicator());
},
errorBuilder: (context, error, stackTrace) => Icon(Icons.error),
)
レイアウトのベストプラクティス
柔軟なレイアウト
- Expanded:Row/Column 内の残りのスペースを埋めます
- Flexible:収まるように縮小します(Expanded と併用しないでください)
- Wrap:はみ出した場合に自動で次の行に折り返します
- SingleChildScrollView:ビューポートより大きい固定サイズのコンテンツに使用します
- ListView.builder/GridView.builder:長いリストに使用します(遅延読み込み)
- FittedBox:親の範囲内に単一の子を拡大縮小して収めます
- LayoutBuilder:利用可能なスペースに応じたレスポンシブレイアウトに使用します
Stack レイアウト
- Positioned:端を基準にして子を正確に配置します
- Align:アラインメント(例:Alignment.center)を使って配置します
オーバーレイ
すべての要素の上に UI 要素を表示するには OverlayPortal を使用します。
final _controller = OverlayPortalController();
OverlayPortal(
controller: _controller,
overlayChildBuilder: (context) => Positioned(
top: 50,
left: 10,
child: Card(child: Text('Overlay content')),
),
child: ElevatedButton(
onPressed: _controller.toggle,
child: Text('Toggle'),
),
)
ビジュアルデザインの原則
デザインガイドライン
- モダンなデザインに沿った、美しく直感的な UI を構築します
- さまざまな画面サイズ(モバイルと Web)に対応させます
- 直感的に操作できるナビゲーションを提供します
- タイポグラフィの階層構造(ヒーローテキスト、見出し、キーワード)を使用します
- さりげない背景テクスチャを適用し、上質な印象を与えます
- 複数層のシャドウを使用して奥行きを表現します
- 理解を助けるアイコンを取り入れます
- インタラクティブな要素には、色のグロー効果を伴うシャドウを付けます
色のガイドライン
- 60-30-10 ルール:60%をプライマリカラー/ニュートラルカラー、30%をセカンダリカラー、10%をアクセントカラーにします
- コントラスト比(WCAG 2.1):
- 通常のテキスト:最低 4.5:1
- 大きなテキスト(18pt または 14pt の太字):最低 3:1
- テキストと背景に補色を使うのは避けます(目の疲れの原因になります)
- アクセントには補色を控えめに使用します
タイポグラフィ
- フォントファミリーは 1~2 種類に制限します
- 読みやすさを優先します(UI の本文にはサンセリフ体を使用します)
- 行の高さ:フォントサイズの 1.4~1.6 倍
- 本文の行の長さ:45~75 文字
- 長文ではすべて大文字にするのを避けます
テスト
テストの種類
- ユニットテスト:ドメインロジック、データレイヤー、状態管理には
package:testを使用します - ウィジェットテスト:UI コンポーネントには
package:flutter_testを使用します - 統合テスト:エンドツーエンドのフローには
package:integration_testを使用します
テストのベストプラクティス
- Arrange-Act-Assert(Given-When-Then)パターンに従います
- より表現力の高いアサーションには
package:checksを優先します - モックよりフェイク/スタブを優先します
- モックが必要な場合は、
mockitoまたはmocktailを使用します - 高いテストカバレッジを目指します
- テスト可能なコードを記述します。依存性注入には
file、process、platformパッケージを使用します
テストの実行
flutter test
アクセシビリティ(A11y)
- 色のコントラスト:テキストと背景のコントラスト比を最低 4.5:1 にします
- 動的なテキストサイズ調整:システムのフォントサイズを大きくして UI をテストします
- セマンティックラベル:明確でわかりやすいラベルを設定するには、
Semanticsウィジェットを使用します - スクリーンリーダーのテスト:TalkBack(Android)と VoiceOver(iOS)でテストします
解析とリンティング
analysis_options.yaml に以下を含めます:
include: package:flutter_lints/flutter.yaml
linter:
rules:
# Add additional lint rules here
追加リソース
詳しいパターンや例については、以下を参照してください:
references/navigation_patterns.md- GoRouter を使用した高度なナビゲーションパターンreferences/state_patterns.md- 状態管理の包括的な例references/theme_patterns.md- 高度なテーマ設定とスタイル設定のパターン
コメント
Markdown 対応