Skip to content

Flutter Advanced Topics

Ce guide couvre les sujets avancés du développement Flutter, incluant la configuration avec Dart define, les techniques de débogage, l’optimisation des performances, les patterns d’architecture avancés, et les considérations pour le build et le déploiement.

Flutter permet de configurer votre application avec des variables d’environnement via --dart-define, utile pour gérer différents environnements (développement, test, production).

Terminal window
flutter run --dart-define=DEBUG=true -d macos
Terminal window
flutter run --dart-define-from-file=./config.json -d macos
const loginTitle = String.fromEnvironment(
'TITLE',
defaultValue: 'Login',
);
const loginColor = int.fromEnvironment(
'BG_COLOR',
defaultValue: 0xFFFCE4EC,
);
const isDebug = bool.fromEnvironment(
'DEBUG',
defaultValue: false,
);

Cette approche permet de :

  • Séparer la configuration du code
  • Gérer différents environnements
  • Sécuriser les clés API et tokens
  • Personnaliser l’application selon le contexte

Flutter propose plusieurs outils intégrés pour faciliter le débogage et l’analyse des performances.

MaterialApp(
// Retire la bannière "DEBUG" en mode debug
debugShowCheckedModeBanner: false,
// Affiche une grille pour aider au positionnement
debugShowMaterialGrid: true,
// Overlay de performance en temps réel
showPerformanceOverlay: true,
// Debugger pour l'accessibilité
showSemanticsDebugger: true,
home: MyHomePage(),
)
  • Flutter Inspector : Analyse de l’arbre de widgets
  • Performance Overlay : Métriques temps réel
  • Network Profiler : Analyse des requêtes réseau
  • Memory Profiler : Gestion mémoire et fuites

L’utilisation appropriée des patterns Observer peut grandement améliorer les performances.

class CounterController extends ValueNotifier<int> {
CounterController(int value) : super(value);
void increment() => value++;
void decrement() => value--;
}
// Utilisation avec ValueListenableBuilder
ValueListenableBuilder(
valueListenable: notifier,
builder: (context, value, child) {
return Text('$value');
},
)
class AsyncCounterViewModel extends ChangeNotifier {
final CounterService service;
AsyncCounterViewModel(this.service) {
init();
}
Future<void> init() async {
_isLoading = true;
notifyListeners();
_count = await service.load();
_isLoading = false;
notifyListeners();
}
int? _count;
int? get count => _count;
bool _isLoading = false;
bool get isLoading => _isLoading;
String? _errorText;
bool get hasError => _errorText != null;
String? get errorText => _errorText;
void increment() async {
_isLoading = true;
notifyListeners();
final newValue = await service.increment();
_count = newValue;
_isLoading = false;
notifyListeners();
}
void decrement() async {
_isLoading = true;
notifyListeners();
final newValue = await service.decrement();
_count = newValue;
_isLoading = false;
notifyListeners();
}
}
  • Minimize rebuilds : Utilisez const constructors quand possible
  • Lazy loading : Chargez les données à la demande
  • Widget disposal : Nettoyez les ressources (timers, streams, controllers)
  • Image optimization : Utilisez des formats appropriés et le cache
  • List virtualization : Pour les longues listes, utilisez ListView.builder

L’utilisation exclusive de setState présente des défis :

  • Difficile à tester
  • Couplage fort entre vue et logique
  • Gestion d’état complexe difficile
  • Réutilisabilité limitée

Flutter propose plusieurs patterns pour organiser les applications :

  • Bloc : Streams et réactivité
  • Provider : Notifiers et injection de dépendances
  • MobX : Réactivité et ViewModel
  • Redux : Store global et flux unidirectionnel
  • Riverpod : Evolution de Provider avec meilleure performance
// MultiProvider pour plusieurs services
MultiProvider(
providers: [
Provider.value(value: userService),
Provider.value(value: messageService),
ChangeNotifierProvider.value(value: authController),
ProxyProvider<AuthController, FeedService>(
update: (_, controller, service) =>
FeedService(FeedClient(controller.user?.token)),
lazy: true,
),
],
child: widget.child,
);
// Utilisation dans les widgets
class MainScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Écoute les changements
final authController = context.watch<AuthController>();
return Scaffold(
appBar: AppBar(
title: Text(authController.user?.name ?? 'Non connecté'),
),
body: MainView(),
);
}
}
sealed class AsyncTask<T> {}
class PendingTask<T> extends AsyncTask<T> {}
class TaskError<T> extends AsyncTask<T> {
final String error;
TaskError([this.error = 'Error !']);
}
class AsyncResult<T> extends AsyncTask<T> {
final T data;
AsyncResult(this.data);
}

Les sealed classes permettent :

  • Exhaustivité : Le compilateur vérifie tous les cas
  • Type safety : Sécurité de typage garantie
  • Pattern matching : Utilisation avec switch expressions
  • États explicites : Modélisation claire des différents états

Pour les packages utilisant la génération de code (comme Freezed, JSON Annotation) :

// Modèle avec Freezed
import 'package:freezed_annotation/freezed_annotation.dart';
part 'message.freezed.dart';
part 'message.g.dart';
@freezed
class Message with _$Message {
const factory Message({
required int id,
required String userName,
required String message,
required DateTime date,
List<Reply>? replies,
}) = _Message;
factory Message.fromJson(Map<String, dynamic> json) =>
_$MessageFromJson(json);
}
Terminal window
# Génération unique
dart run build_runner build
# Génération en mode watch (regénère automatiquement)
dart run build_runner watch
Terminal window
# Génération des informations de couverture
flutter test --coverage
# Dans IDE (IntelliJ/Android Studio)
# Run... with Coverage

Analyse de la couverture avec genhtml :

Terminal window
genhtml coverage/lcov.info -o coverage
open coverage/index.html

Configuration pour les tests end-to-end :

dev_dependencies:
integration_test:
sdk: flutter

Les tests d’intégration permettent de :

  • Tester l’application complète
  • Vérifier les interactions utilisateur
  • Valider les flux critiques
  • Automatiser les tests de régression

Pour tester le rendu visuel :

// Package Alchemist pour simplifier les golden tests
dependencies:
alchemist: ^0.7.0

Les golden tests permettent de :

  • Détecter les régressions visuelles
  • Valider le rendu sur différentes plateformes
  • Automatiser les tests d’interface
  • Maintenir la cohérence visuelle

Ressources vidéo recommandées :

  1. Configuration : Utilisez --dart-define pour gérer les environnements
  2. Débogage : Activez les outils de débogage appropriés selon le contexte
  3. Performance : Profilez régulièrement et optimisez les goulots d’étranglement
  4. Architecture : Choisissez un pattern adapté à la complexité de votre projet
  5. Tests : Implémentez une stratégie de test complète (unitaires, widgets, intégration)
  6. Déploiement : Automatisez les builds et la génération de code
  7. Monitoring : Surveillez les performances en production

Ces sujets avancés vous permettront de créer des applications Flutter robustes, maintenables et performantes, prêtes pour la production.