skills/flutter-performance/SKILL.md
Flutter アプリのパフォーマンス最適化(const・rebuild削減・メモリ・Image)のベストプラクティス
npx skillsauth add oto1720/claude-agents-skills flutter-performanceInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
# Profile モードで実行(Release に近い計測)
flutter run --profile
# DevTools で確認
# - Performance タブ: UI/Raster スレッド
# - Widget Inspector: rebuild カウント
# - Memory タブ: ヒープ使用量
Flutter の最重要最適化。const Widget はビルド時に作成され、再利用される。
// ❌ 毎フレーム新しいインスタンス生成
Padding(
padding: EdgeInsets.all(16),
child: Text('Hello'),
)
// ✅ const: 1回だけ生成・キャッシュ
const Padding(
padding: EdgeInsets.all(16),
child: Text('Hello'),
)
検出コマンド:
# const を付けられるのに付いていない箇所
flutter analyze 2>&1 | grep "prefer_const"
# analysis_options.yaml で警告を有効化
# rules:
# prefer_const_constructors: true
# prefer_const_literals_to_create_immutables: true
// ❌ State が不要なのに StatefulWidget
class UserAvatar extends StatefulWidget {
@override
State<UserAvatar> createState() => _UserAvatarState();
}
// ✅ StatelessWidget で十分
class UserAvatar extends StatelessWidget {
const UserAvatar({super.key, required this.url});
final String url;
@override
Widget build(BuildContext context) => CircleAvatar(
backgroundImage: NetworkImage(url),
);
}
// ❌ build 毎に重い計算
class UserList extends StatelessWidget {
@override
Widget build(BuildContext context) {
// rebuild のたびに全件フィルタリング
final activeUsers = users.where((u) => u.isActive).toList();
return ListView.builder(itemCount: activeUsers.length, ...);
}
}
// ✅ Notifier / Provider で計算・キャッシュ
@riverpod
List<User> activeUsers(ActiveUsersRef ref) {
final users = ref.watch(usersProvider);
return users.where((u) => u.isActive).toList();
}
class UserList extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final activeUsers = ref.watch(activeUsersProvider);
return ListView.builder(itemCount: activeUsers.length, ...);
}
}
// ❌ 全アイテムを一度に生成
ListView(children: items.map((i) => ItemWidget(i)).toList())
// ✅ 必要な分だけ生成(Lazy Loading)
ListView.builder(
itemCount: items.length,
itemBuilder: (_, index) => ItemWidget(items[index]),
)
// ✅ 固定高さなら itemExtent で更に最適化
ListView.builder(
itemCount: items.length,
itemExtent: 72.0, // 全アイテムが同じ高さの場合
itemBuilder: (_, index) => ItemWidget(items[index]),
)
// ✅ 分離線付きリスト
ListView.separated(
itemCount: items.length,
separatorBuilder: (_, __) => const Divider(),
itemBuilder: (_, index) => ItemWidget(items[index]),
)
// アニメーションが他の Widget に影響しないように
RepaintBoundary(
child: AnimatedWidget(),
)
// スクロールリスト内の複雑な Widget
ListView.builder(
itemBuilder: (_, index) => RepaintBoundary(
child: ComplexCard(item: items[index]),
),
)
// ✅ cacheWidth/cacheHeight でメモリ節約
Image.network(
url,
cacheWidth: 300, // 表示サイズに合わせる
cacheHeight: 300,
fit: BoxFit.cover,
)
// ✅ CachedNetworkImage パッケージを使用(ディスクキャッシュ)
CachedNetworkImage(
imageUrl: url,
placeholder: (_, __) => const ShimmerPlaceholder(),
errorWidget: (_, __, ___) => const Icon(Icons.error),
)
// ✅ precacheImage で事前読み込み
@override
void initState() {
super.initState();
precacheImage(NetworkImage(imageUrl), context);
}
// ❌ State 全体を監視 → 関係ない変更でも rebuild
final user = ref.watch(userProvider);
return Text(user.name); // email が変わっても rebuild される
// ✅ 必要なフィールドのみ監視
final name = ref.watch(userProvider.select((u) => u.name));
return Text(name); // name が変わったときのみ rebuild
// ❌ dispose 後に setState → エラー
void _loadData() async {
final data = await fetchData();
setState(() => _data = data); // dispose 済みかもしれない
}
// ✅ mounted チェック
void _loadData() async {
final data = await fetchData();
if (mounted) {
setState(() => _data = data);
}
}
| 項目 | チェック | |------|---------| | const Widget が使われている | [ ] | | build() 内で重い計算をしていない | [ ] | | ListView.builder を使っている | [ ] | | 不要な StatefulWidget がない | [ ] | | Image に cacheWidth/cacheHeight がある | [ ] | | Riverpod で select を使っている | [ ] | | Profile モードで60FPS 維持 | [ ] |
| 原因 | 症状 | 対処 | |------|------|------| | build() 内の重い処理 | スクロール時に詰まる | Isolate / compute に移動 | | 大きな画像をそのまま表示 | 初回表示が遅い | cacheWidth/cacheHeight | | 非同期処理を UI スレッドでブロック | フリーズ | compute / Isolate | | 過剰な setState | 全体が再描画 | 最小粒度で setState | | 透明度アニメーション | GPU 負荷 | AnimatedOpacity より FadeTransition |
development
プロジェクト全体の技術構成図(アーキテクチャダイアグラム)を自動生成するスキル。リポジトリやプロジェクトのコードベースを解析し、使用技術・依存関係・レイヤー構造・データフロー・インフラ構成を可視化したMermaid/SVG/HTML図を生成する。「技術構成図を作って」「アーキテクチャ図」「システム構成を可視化」「プロジェクトの全体像」「tech stack diagram」などのリクエストで必ずこのスキルを使用すること。プロジェクトの理解・オンボーディング資料・ドキュメント作成にも活用できる。
testing
Create new skills, modify and improve existing skills, and measure skill performance. Use when users want to create a skill from scratch, update or optimize an existing skill, run evals to test a skill, benchmark skill performance with variance analysis, or optimize a skill's description for better triggering accuracy.
development
セキュリティ観点でコードを精査し、脆弱性・リスクをレポートする。 以下のトリガーで自動発動: - 「セキュリティレビューして」「脆弱性チェック」「セキュリティ問題ない?」 - 「認証コードを確認して」「APIキーや秘密情報が漏れていないか確認して」 - /security-review [ファイルパス]
tools
PRやコミットの差分をレビューして、マージ可否の判断と指摘事項を出力する。 以下のトリガーで自動発動: - 「PRレビューして」「このPRどう思う?」「マージしても大丈夫?」 - 「差分をレビューして」「コミット内容を確認して」 - /pr-review [ブランチ名 or コミットハッシュ]