Bug Report Flutter : le guide complet pour signaler et résoudre les bugs
Un bug report Flutter efficace peut réduire le temps de résolution de plus de 60%. Ce guide complet détaille comment reproduire, documenter et signaler un bug Flutter : collecte de la stack trace, des informations de device, captures d'écran annotées, et intégration d'un SDK de feedback in-app comme SnapLogs. Vous y trouverez des best practices, un workflow de triage, des exemples de code Dart prêts à l'emploi et des modèles de ticket exploitables par toute l'équipe.
SnapLogs Team
Équipe produit

Bug Report Flutter : le guide complet pour signaler et résoudre les bugs
Un bug report Flutter mal écrit peut coûter des heures, voire des jours, à une équipe. À l'inverse, un bug report précis, reproductible et richement contextualisé permet de passer directement de la découverte à la résolution. Dans ce guide complet, nous allons décortiquer chaque étape d'un bug report Flutter professionnel : de la reproduction du défaut à la collecte de la stack trace, en passant par les captures d'écran annotées, les informations de device, et l'intégration d'un SDK de feedback in-app comme SnapLogs. Que vous soyez développeur solo, QA, ou membre d'une équipe produit, ce guide vous donnera un workflow concret et des exemples de code Dart prêts à l'emploi.
Pourquoi un bon bug report Flutter change tout
Le bug report est l'interface entre l'utilisateur qui subit un problème et le développeur qui doit le corriger. Plus cette interface est bruyante, plus le temps de résolution augmente. Selon les données internes que nous observons sur SnapLogs, un ticket sans stack trace ni étapes de reproduction demande en moyenne 4,2 allers-retours supplémentaires par rapport à un ticket complet.
Le coût est triple :
- Coût temps : chaque allers-retour coûte 15 à 45 minutes de contexte recharge.
- Coût confiance : l'utilisateur final perçoit une équipe qui « tâte dans le noir ».
- Coût produit : pendant que le dev reproduit, un autre bug attend.
Flutter, parce qu'il compile vers du natif (ARM, x86_64) et du web, ajoute une complexité : un même widget peut se comporter différemment selon la plateforme cible. Le bug report doit donc capter la plateforme, le mode de build et le contexte d'exécution.

Le contexte technique complet (stack trace + breadcrumbs + screenshot) divise le temps de resolution par 20 par rapport a un bug report sans contexte.
Les composants d'un bug report Flutter complet
Un bug report Flutter de qualité repose sur six piliers. Aucun n'est facultatif pour un ticket exploitable.
1. Un titre descriptif et unique
Le titre doit contenir :
- La zone fonctionnelle (ex :
PaymentScreen). - Le symptôme observé (ex :
crash on tap "Pay"). - La plateforme si pertinente (ex :
Android 13).
Mauvais titre : « Bug sur le paiement » Bon titre : «
PaymentScreen: crash sur onTap Pay (Android 13, Pixel 6) »
2. Les étapes de reproduction
Listez les étapes comme une recette. Chaque étape doit être atomique et vérifiable.
- Lancer l'app depuis un état froid (
flutter run). - Naviguer vers
/paymentsdepuis le bottom nav. - Sélectionner un montant de 49,99 €.
- Appuyer sur le bouton « Payer ».
- Observer : l'app crash 300 ms après le tap, sans message d'erreur visible.
Si le bug n'est pas reproductible à 100%, précisez la fréquence (ex : « 7 crashes sur 10 essais ») et les conditions suspectées (réseau lent, état d'auth).
3. La stack trace
La stack trace est le cœur du diagnostic. En Flutter, elle provient de plusieurs sources :
- Framework errors : capturées via
FlutterError.onError. - Async errors : capturées via
Zone.current.forkourunZonedGuarded. - Isolate errors :
Isolate.current.addErrorListener.
Voici un pattern robuste pour tout capter :
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:snaplogs/snaplogs.dart';
Future<void> main() async {
await SnapLogs.init(apiKey: const String.fromEnvironment('SNAPLOGS_KEY'));
FlutterError.onError = (FlutterErrorDetails details) {
FlutterError.presentError(details);
SnapLogs.captureException(details.exception, stackTrace: details.stack);
};
runZonedGuarded<Future<void>>(() async {
runApp(const MyApp());
}, (Object error, StackTrace stack) {
SnapLogs.captureException(error, stackTrace: stack);
});
Isolate.current.addErrorListener(
RawReceivePort((dynamic pair) {
final List<dynamic> data = pair as List<dynamic>;
SnapLogs.captureException(
data.first as Object,
stackTrace: data.last as StackTrace,
);
}).sendPort,
);
}
4. Les informations de device et d'environnement
Une stack trace sans contexte ne suffit jamais. Il faut capturer :
| Champ | Valeur attendue | Source Dart |
| --- | --- | --- |
| Plateforme | ios / android / web | defaultTargetPlatform |
| Version OS | 16.4, Android 13 | Platform.operatingSystemVersion |
| Modèle device | iPhone 14 Pro, Pixel 6 | device_info_plus |
| Version Flutter | 3.22.1 | flutter --version |
| Version SDK Dart | 3.4.0 | Platform.version |
| Mode build | debug / profile / release | kReleaseMode |
| Densité écran | 3.0x | MediaQuery.devicePixelRatio |
| Taille écran | 393 × 852 | MediaQuery.size |
| Langue | fr_FR | Platform.localeName |
| Réseau | wifi / 4G | connectivity_plus |
Un exemple d'utilitaire à intégrer dans votre bug report :
import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/foundation.dart';
Future<Map<String, dynamic>> collectEnvironment() async {
final deviceInfo = DeviceInfoPlugin();
final base = <String, dynamic>{
'flutter': '3.22.1',
'dart': Platform.version,
'mode': kReleaseMode ? 'release' : (kProfileMode ? 'profile' : 'debug'),
'locale': Platform.localeName,
};
if (Platform.isIOS) {
final ios = await deviceInfo.iosInfo;
base.addAll({
'platform': 'ios',
'device': ios.utsname.machine,
'os': ios.systemVersion,
});
} else if (Platform.isAndroid) {
final android = await deviceInfo.androidInfo;
base.addAll({
'platform': 'android',
'device': android.model,
'os': android.version.release,
});
}
return base;
}
5. La capture visuelle
Une capture d'écran annotée vaut mille tickets. Le visuel permet :
- De voir exactement l'état du widget au moment du bug.
- De détecter des problèmes de layout invisibles dans les logs (
Overflow,RenderFlex). - De réduire la friction côté utilisateur : pas besoin d'écrire, on annote.
SnapLogs propose une capture native avec annotation, où l'utilisateur peut dessiner, entourer et commenter directement sur la capture. Voir la page plateforme Flutter pour le détail d'intégration.
6. Le contexte technique (breadcrumbs et logs)
Le breadcrumbs, c'est l'historique des actions qui ont mené au bug. Exemple :
12:04:31 TAP "Login button"
12:04:31 NAV push /dashboard
12:04:32 NETWORK POST /auth/token → 200 (412ms)
12:04:34 NAV push /payments
12:04:34 NETWORK GET /payments/methods → 200 (89ms)
12:04:35 TAP "Pay"
12:04:35 NETWORK POST /payments/charge → 500 (203ms)
12:04:35 ERROR PaymentException: provider declined
Sans ce breadcrumbs, le dev voit uniquement « provider declined ». Avec, il sait que le user vient de se loguer et que c'est la première charge de la session : piste immédiate, jeton expiré ou fraude rate-limit.
Les outils de bug report Flutter en 2026
Trois familles d'outils coexistent. Chacune a son rôle.
Flutter DevTools (natif)
DevTools est intégré à Flutter. Il expose :
- Widget Inspector : inspecte l'arbre des widgets.
- Performance : flame charts, GPU threads.
- Memory : heap snapshots, leak tracking.
- Network : requêtes HTTP captées par
HttpClient.
DevTools est idéal pendant le dev local mais ne capture rien en production. Pour le bug report utilisateur, il faut un service externe.
Sentry, Bugsnag, Firebase Crashlytics
Ces outils se concentrent sur les crashs. Ils capturent la stack trace, l'environnement, et groupent les incidents. Mais :
- Ils ne capturent pas l'écran.
- Ils ne couvrent pas les bugs visuels (layout, UI).
- Ils n'offrent pas de feedback in-app pour l'utilisateur.
Ils sont parfaits en complément, pas en remplacement.
SnapLogs : le feedback visuel in-app
SnapLogs combine :
- Capture d'écran + annotation par l'utilisateur.
- Breadcrumbs automatiques (taps, navigations, réseau).
- Logs structurés attachés au rapport.
- Envoi vers votre back (Slack, Linear, n8n).
C'est l'outil idéal pour les bugs visuels et logiques que les crash reporters ratent. Pour comparer en détail, voir notre comparatif des bug trackers Flutter.
Workflow de triage d'un bug report
Un bug report arrive. Que faire ? Voici le workflow que nous recommandons.
Étape 1 : qualifier le bug
Posez 3 questions :
- Est-ce reproductible ?
- Quel est l'impact (blocker, major, minor, cosmetic) ?
- Quelle est la fréquence (1/10, 1/100, systématique) ?
Étape 2 : enrichir le rapport
Si le ticket manque d'infos, ne demandez pas tout d'un coup. Envoyez un message court :
« Peux-tu secouer le téléphone pour ouvrir SnapLogs et renvoyer le rapport ? Le SDK va joindre les logs automatiquement. »
Cette friction est réduite à zéro si vous avez intégré le SDK. Voyez la doc d'intégration.
Étape 3 : reproduire en local
Clonez le repo à la version indiquée. Reproduisez les étapes. Si ça ne reproduit pas :
- Essayez en
profilemode (ledebugmasque des perfs). - Comparez device réel vs simulateur (les GPU diffèrent).
- Vérifiez le réseau (utilisez un proxy Charles avec throttling).
Étape 4 : isoler la cause
Utilisez le widget inspector et les debugPrint. Ajoutez des assert pour confirmer vos hypothèses :
assert(
payment != null,
'Payment was null at charge time. Last breadcrumb: $lastBreadcrumb',
);
Étape 5 : documenter le fix
Une fois corrigé, ajoutez au ticket :
- Le commit hash (
abc1234). - Le test qui couvre le cas.
- Le test ID du widget (
Key('payment_button')).
Cela transforme le bug report en précedent réutilisable.
Exemples de code Dart pour un bug report complet
Capture d'exception avec contexte
try {
await chargeCustomer(amount: 49.99, method: 'card_visa');
} on PaymentException catch (e, st) {
await SnapLogs.captureException(
e,
stackTrace: st,
tags: {'flow': 'payment', 'version': '1.4.2'},
extra: {
'amount': 49.99,
'method': 'card_visa',
'last_breadcrumb': SnapLogs.breadcrumbs.last,
},
);
rethrow;
}
Déclenchement manuel du feedback
import 'package:flutter/material.dart';
import 'package:snaplogs/snaplogs.dart';
class FeedbackFab extends StatelessWidget {
const FeedbackFab({super.key});
@override
Widget build(BuildContext context) {
return FloatingActionButton(
onPressed: () => SnapLogs.showFeedbackSheet(context),
child: const Icon(Icons.bug_report_outlined),
);
}
}
Activation du shake-to-report
import 'package:flutter/material.dart';
import 'package:snaplogs/snaplogs.dart';
Future<void> main() async {
await Snaplogs.init(
apiKey: const String.fromEnvironment('SNAPLOGS_KEY'),
shakeToReport: true,
shakeThreshold: 2.7, // intensité du shake
);
runApp(const MyApp());
}
Gestion des erreurs asynchrones
Future<void> fetchUserOrders(String userId) async {
try {
final orders = await api.get('/users/$userId/orders');
_orders.addAll(orders);
} catch (e, st) {
// Même les erreurs "swallowées" doivent être tracées
await SnapLogs.captureException(
e,
stackTrace: st,
level: Severity.warning,
tags: {'flow': 'orders_fetch'},
);
}
}
Best practices de bug report Flutter
Voici une checklist que vous pouvez reprendre dans votre équipe.
Côté développeur
- Toujours logger avec un niveau (
debug,info,warning,error,fatal). - Ne jamais logger de données personnelles (email, phone, token). Utilisez la redaction.
- Toujours wrapper les appels réseau dans un
try/catchet tracer l'échec. - Toujours tester un widget avec une
Keypour faciliter le rapport. - Toujours versionner le schema de données (ex :
schemaVersion: 2).
Côté QA / support
- Toujours demander le mode build (
releasevsdebug). - Toujours demander le modèle de device exact, pas « un Android ».
- Toujours demander une capture vidéo si le bug est visuel ou intermittent.
- Toujours demander le moment précis (timestamp, heure locale).
Côté produit
- Définir un seuil de déduplication (ne pas créer 50 tickets pour le même crash).
- Définir un SLA de triage (ex : 24h ouvrées pour les major, 1h pour les blockers).
- Définir un format de ticket obligatoire dans Linear ou Jira.
Intégration du SDK SnapLogs Flutter
SnapLogs expose un package Flutter officiel. Voici l'intégration complète.
1. Ajout de la dépendance
Dans pubspec.yaml :
dependencies:
flutter:
sdk: flutter
snaplogs: ^2.4.0
device_info_plus: ^10.1.0
connectivity_plus: ^6.0.3
2. Initialisation dans main.dart
import 'package:flutter/material.dart';
import 'package:snaplogs/snaplogs.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await SnapLogs.init(
apiKey: const String.fromEnvironment('SNAPLOGS_KEY'),
environment: 'production',
captureLayoutWarnings: true,
redactPatterns: [r'\b\d{16}\b', r'\b[\w.]+@[\w.]+\b'],
sampleRate: 1.0,
beforeSend: (event) {
if (event.tags['flow'] == 'health_check') return null;
return event;
},
);
FlutterError.onError = (details) {
FlutterError.presentError(details);
SnapLogs.captureException(details.exception, stackTrace: details.stack);
};
runApp(const MyApp());
}
3. Déclenchement du feedback
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('SnapLogs demo')),
body: const Center(child: Text('Secoue ton téléphone')),
floatingActionButton: FloatingActionButton(
onPressed: () => SnapLogs.showFeedbackSheet(context),
child: const Icon(Icons.bug_report),
),
),
);
}
}
4. Test de l'intégration
Pour vérifier que tout remonte :
ElevatedButton(
onPressed: () => throw Exception('test_snaplogs_integration'),
child: const Text('Crash test'),
)
Vous devriez voir le rapport apparaître dans votre dashboard SnapLogs en moins de 5 secondes. Si ce n'est pas le cas, vérifiez votre clé API et le environment.
Analyser une stack trace Flutter
Une stack trace Flutter a une structure particulière. Apprendre à la lire accélère le diagnostic de 50%.
Structure d'une stack
#0 PaymentProvider.charge (package:app/payments.dart:84)
#1 _PaymentScreenState._onPay (package:app/screens/payment.dart:42)
#2 GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:312)
#3 TapGestureRecognizer.handleTapUp (package:flutter/src/gestures/tap.dart:612)
Lecture :
#0est le frame le plus profond (la cause immédiate).- Les frames
package:app/...sont votre code. - Les frames
package:flutter/...sont le framework. - Les frames sans package sont du Dart natif ou de l'obfusqué.
Identifier le fautif
Le frame le plus profond dans package:app/ est votre point d'entrée fautif. Dans l'exemple, payments.dart:84. Ouvrez ce fichier à cette ligne. C'est souvent là que se trouve le bug, pas plus bas.
Distinguer erreur sync et async
Une stack sync est linéaire. Une stack async a des _rootRun et FutureListener :
#0 HttpClient.send (package:http/http.dart:124)
#1 fetchUserOrders.<anonymous closure> (package:app/orders.dart:58)
#2 _rootRun (dart:async/zone.dart:1412)
Ici, la cause est orders.dart:58, pas http.dart:124 (le HTTP lib ne faut pas).
Cas de l'obfuscation
En release, vous voyez package:app/main.dart:0x4a3b2c1. Sans symbols, vous ne pouvez pas diag. D'où l'importance d'uploader les symbols à chaque release (voir le guide de debug production).
Déboguer les erreurs de layout invisibles
Flutter génère des erreurs silencieuses de layout (Overflow, RenderFlex). Elles apparaissent en console mais pas dans l'UI. Pour les capter :
FlutterError.onError = (FlutterErrorDetails details) {
FlutterError.presentError(details);
SnapLogs.captureException(
details.exception,
stackTrace: details.stack,
tags: {'flow': 'layout'},
);
};
Sans cela, un overflow sur iPhone SE peut passer inaperçu pendant des semaines. Avec SnapLogs, il remonte comme un crash classique et vous le corrigez en moins d'une journée.
Workflow de collaboration avec le support
Le bug report n'est pas qu'un problème de dev. C'est aussi un workflow inter-équipes. Voici un workflow typique avec SnapLogs.
Côté support
Le support reçoit un email « ça ne marche pas ». Au lieu de demander 10 infos, il répond :
« Merci de secouer votre téléphone et de décrire le bug en une phrase. Le système va nous envoyer tout ce qu'il faut. »
L'utilisateur secoue, annote, et le ticket part pré-rempli.
Côté QA
Le QA voit le ticket dans Linear avec la capture et les breadcrumbs. Il qualifie :
- Sévérité (blocker, major, minor).
- Reproductibilité (oui/non/partielle).
- Assignation (frontend, backend, design).
Côté dev
Le dev ouvre le ticket, a la capture sous les yeux, les breadcrumbs en timeline, et la stack si présente. Il n'a plus à demander d'infos. Il code le fix.
Côté produit
Le produit voit les métriques dans le dashboard SnapLogs :
- Nombre de rapports par jour.
- Top features buguées.
- Temps moyen de résolution.
Ce workflow réduit le temps de cycle de 6h à 1h en moyenne.
Modèle de ticket de bug Flutter
Voici un template à copier dans Linear, Jira ou GitHub Issues.
### Titre
PaymentScreen : crash sur onTap Pay (Android 13, Pixel 6)
### Environnement
- Flutter : 3.22.1
- Dart : 3.4.0
- Plateforme : Android 13, Pixel 6
- Build mode : release
- App version : 1.4.2 (build 184)
### Reproduction
1. Lancer l'app à froid
2. Naviguer vers /payments
3. Sélectionner 49.99 EUR
4. Taper "Payer"
5. Crash 300ms après le tap
### Fréquence
7 crashes sur 10 essais
### Stack trace (extrait)
#0 PaymentProvider.charge (package:app/payments.dart:84)
#1 _PaymentScreenState._onPay (package:app/screens/payment.dart:42)
#2 GestureRecognizer.invokeCallback (package:flutter/.../gesture.dart:312)
### Breadcrumbs (SnapLogs)
12:04:34 NAV push /payments
12:04:34 NET GET /payments/methods → 200
12:04:35 TAP "Pay"
12:04:35 NET POST /payments/charge → 500
12:04:35 ERR PaymentException: provider declined
### Capture
Voir pièce jointe (annotation SnapLogs)
### Impact
Blocker : bloque 8% des checkouts depuis la 1.4.0.
Erreurs courantes dans les bug reports Flutter
Confondre debug et release
Un crash qui n'apparaît qu'en release est typiquement lié à l'obfuscation ou à du code mort supprimé par le tree-shaking. Gardez vos symbols.map et configurez la symbolication côté serveur.
Oublier le mode web
Flutter Web a des comportements spécifiques : CORS, layout viewport, pas de Platform.isAndroid. Un bug report web doit préciser le navigateur (Chrome 120, Safari 17) et la résolution.
Ne pas redédupliquer
Un même crash peut générer 100 tickets. Utilisez le groupage côté serveur (SnapLogs le fait par signature de stack) et le fingerprint personnalisé :
await SnapLogs.captureException(
e,
stackTrace: st,
fingerprint: ['payment', 'charge', 'provider_declined'],
);
Logger du PII
Logger un email, un token ou un IBAN est une violation RGPD. Utilisez la redaction par regex :
await SnapLogs.init(
apiKey: key,
redactPatterns: [
r'\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b', // email
r'\b(?:FR|DE|GB)\d{2}[A-Z0-9]{12,30}\b', // IBAN
],
);
Comment SnapLogs automatise tout ce workflow
SnapLogs a été pensé pour réduire la friction du bug report à néant. Dès que l'utilisateur secoue son téléphone :
- Une capture d'écran est prise.
- L'utilisateur annote (cercle, flèche, texte).
- Le SDK attache automatiquement les breadcrumbs, les logs, les infos device.
- Le rapport part vers votre back (Slack, Linear, n8n).
- Le ticket arrive pré-rempli dans votre tracker.
Côté dev, vous avez un ticket complet en moins de 10 secondes, sans avoir relancé l'utilisateur une seule fois. C'est le seul outil qui combine crash reporting, feedback visuel et logs structurés pour Flutter. Pour essayer, créez un compte gratuit ou consultez la documentation SDK Flutter.
Conclusion
Un bon bug report Flutter n'est pas une question de talent, mais de système. Avec les bons outils (DevTools en local, SnapLogs en production), un workflow clair (reproduction, stack trace, breadcrumbs, capture visuelle), et un format de ticket standardisé, vous pouvez diviser par 3 votre temps moyen de résolution. Le secret n'est pas de travailler plus, mais de capter le contexte au moment où le bug se produit, avant qu'il ne s'évapore.
Les points à retenir :
- Un titre descriptif et unique.
- Des étapes de reproduction atomiques.
- Une stack trace captée pour framework, async et isolate.
- Le contexte device + build mode.
- Une capture visuelle annotée.
- Des breadcrumbs et logs attachés.
Si vous voulez mettre tout cela en place en moins de 30 minutes, intégrez le SDK SnapLogs Flutter et activez le shake-to-report. Vos utilisateurs deviendront vos meilleurs QA, sans effort.
Pour aller plus loin, lisez notre comparatif des bug trackers Flutter et notre guide de debug Flutter en production. Pour découvrir la plateforme, consultez la page dédiée à Flutter, nos prix, ou notre documentation.
Articles liés
Installez le SDK Flutter SnapLogs
Capture d'ecran annotee, logs contexte, feedback in-app. 5 minutes d'integration.
Voir la doc Flutter