💻 Développement

dev-flutter-helper

Aide au développement Flutter/Dart avec bonnes pratiques, patterns, snippets copiables et diagnostics d'erreurs.

⚡ Installation & lancement en 1 commande

Copiez-collez dans votre terminal : le skill s'installe dans ~/.claude/skills et Claude Code se lance directement dessus.

macOS / Linux
curl -fsSL https://raw.githubusercontent.com/khalilbenaz/claude-skills-collection/main/install.sh | sh -s -- dev-flutter-helper --launch
Windows (PowerShell)
iex "& { $(iwr -useb https://raw.githubusercontent.com/khalilbenaz/claude-skills-collection/main/install.ps1) } dev-flutter-helper -Launch"

🚀 Déjà installé ?

claude "/dev-flutter-helper"

Ou tapez /dev-flutter-helper dans une session Claude Code, ou décrivez simplement votre besoin — le skill se déclenche automatiquement via le skill-router.

🔑 Déclencheurs automatiques

Le skill s'active automatiquement quand votre demande contient :

FlutterDartWidgetBLoCRiverpodProviderpub.devMaterialAppflutter build

📦 Installation manuelle

git clone https://github.com/khalilbenaz/claude-skills-collection.git cp -r claude-skills-collection/skills/dev-flutter-helper ~/.claude/skills/

Payload du plugin : skills/dev-flutter-helper · source éditable : dev-skills/flutter-helper

📖 Manuel

Flutter Helper

1. Diagnostic initial

Avant tout, collecter :


2. Structure projet

Feature-first (projets moyens/grands) :

lib/
  features/
    auth/
      data/        # repositories, data sources, models
      domain/      # entities, use cases
      presentation/ # pages, widgets, controllers
  core/            # DI, router, theme, utils
  main.dart

Layer-first (petits projets ou prototypes) :

lib/
  data/ domain/ presentation/
  main.dart

Critère de choix : si > 3 features ou > 2 devs → feature-first obligatoire.


3. State management — décision

Taille projetSolution recommandéeQuand éviter
Prototype / très petitProvider> 3 features
MoyenRiverpod 2.xjamais — convient toujours
Grande logique métierBLoC/Cubitsur-architecture sur petits cas
Full-stack légerGetXmaintainability sacrifiée

Riverpod — pattern minimal fonctionnel :

// counter_provider.dart
final counterProvider = StateNotifierProvider<CounterNotifier, int>(
  (ref) => CounterNotifier(),
);

class CounterNotifier extends StateNotifier<int> {
  CounterNotifier() : super(0);
  void increment() => state++;
}

// widget
class CounterPage extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final count = ref.watch(counterProvider);
    return Text('$count');
  }
}

Cubit — pattern minimal :

class AuthCubit extends Cubit<AuthState> {
  AuthCubit(this._repo) : super(AuthInitial());
  final AuthRepository _repo;

  Future<void> login(String email, String password) async {
    emit(AuthLoading());
    try {
      final user = await _repo.login(email, password);
      emit(AuthSuccess(user));
    } catch (e) {
      emit(AuthError(e.toString()));
    }
  }
}

4. Widgets — règles de performance

// ✅ Bon : const constructor évite le rebuild
const MyCard({super.key, required this.title});

// ✅ Isoler le Consumer au plus proche de la donnée
Consumer<CartModel>(
  builder: (_, cart, __) => Text('${cart.itemCount}'),
);

// ❌ Mauvais : Consumer sur tout l'arbre
Consumer<CartModel>(
  builder: (_, cart, __) => Scaffold(...),  // rebuild = toute la page
);

// ✅ ListView.builder pour listes longues
ListView.builder(
  itemCount: items.length,
  itemBuilder: (_, i) => ItemTile(item: items[i]),
);

Règle : jamais de logique métier dans build() — uniquement présentation.


5. Navigation — GoRouter (recommandé 2025+)

# pubspec.yaml
dependencies:
  go_router: ^14.0.0
final router = GoRouter(
  initialLocation: '/home',
  redirect: (context, state) {
    final loggedIn = ref.read(authProvider).isLoggedIn;
    if (!loggedIn && state.matchedLocation != '/login') return '/login';
    return null;
  },
  routes: [
    GoRoute(path: '/login', builder: (_, __) => const LoginPage()),
    GoRoute(
      path: '/home',
      builder: (_, __) => const HomePage(),
      routes: [
        GoRoute(path: 'detail/:id', builder: (_, s) => DetailPage(id: s.pathParameters['id']!)),
      ],
    ),
  ],
);

// Navigation
context.go('/home/detail/42');
context.push('/home/detail/42');  // ajoute au back stack

6. Networking + modèles

dependencies:
  dio: ^5.4.0
  retrofit: ^4.1.0
  freezed_annotation: ^2.4.1
  json_annotation: ^4.9.0
dev_dependencies:
  build_runner: ^2.4.9
  freezed: ^2.4.7
  json_serializable: ^6.7.1
  retrofit_generator: ^8.1.0
// model généré via freezed
@freezed
class User with _$User {
  const factory User({
    required int id,
    required String name,
    String? avatar,
  }) = _User;
  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}

// génération : flutter pub run build_runner build --delete-conflicting-outputs

Intercepteur Dio pour token :

dio.interceptors.add(InterceptorsWrapper(
  onRequest: (options, handler) {
    options.headers['Authorization'] = 'Bearer $token';
    handler.next(options);
  },
));

7. Testing

// Widget test
testWidgets('affiche le titre', (tester) async {
  await tester.pumpWidget(const MaterialApp(home: MyWidget(title: 'Test')));
  expect(find.text('Test'), findsOneWidget);
});

// BLoC/Cubit test
blocTest<AuthCubit, AuthState>(
  'émet AuthSuccess après login réussi',
  build: () => AuthCubit(mockRepo),
  act: (c) => c.login('u@test.com', 'pass'),
  expect: () => [isA<AuthLoading>(), isA<AuthSuccess>()],
);

Commandes utiles :

flutter test                          # tous les tests
flutter test --coverage               # avec couverture
genhtml coverage/lcov.info -o cov_html # rapport HTML
flutter test integration_test/        # tests d'intégration

8. Performance — checklist

// compute() = Isolate simplifié
final result = await compute(parseJsonHeavy, rawString);

9. Build & déploiement

# flavors via dart-define
flutter run --dart-define=ENV=staging
flutter build apk --dart-define=ENV=prod --release

# APK split par ABI (taille réduite)
flutter build apk --split-per-abi

# iOS archive
flutter build ipa --release

# Versions automatiques
flutter pub version          # avec package cider

Flavors dans le code :

const env = String.fromEnvironment('ENV', defaultValue: 'dev');

10. Anti-patterns / pièges à éviter

PiègeSolution
setState dans un StatelessWidgetPasser à StatefulWidget ou state manager
BuildContext utilisé après awaitVérifier mounted avant toute opération post-await
Appels API dans build()Déplacer dans initState ou un provider
Image.network() sans cacheUtiliser CachedNetworkImage
Packages abandonnés (vérifier pub.dev)Filtrer par "Likes > 100" et "Maintained"
print() en productionRemplacer par debugPrint() ou logger package
Nested ScaffoldUn seul Scaffold par route
// ✅ Vérification mounted après async
Future<void> fetchData() async {
  final data = await api.get();
  if (!mounted) return;          // évite memory leak / crash
  setState(() => _data = data);
}