SnapLogs
Retour au blog
Flutter

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.

S

SnapLogs Team

Équipe produit

Bug Report Flutter : le guide complet pour signaler et résoudre les bugs

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.

Temps moyen de resolution selon le niveau de contexte

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 /payments depuis 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.fork ou runZonedGuarded.
  • 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 profile mode (le debug masque 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/catch et tracer l'échec.
  • Toujours tester un widget avec une Key pour faciliter le rapport.
  • Toujours versionner le schema de données (ex : schemaVersion: 2).

Côté QA / support

  • Toujours demander le mode build (release vs debug).
  • 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 :

  • #0 est 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.

#bug-report#flutter#debugging#dart#sdk#screenshot#errors#testing

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