Flutter:本番環境での灰色の死の画面を削除する

Flutter:本番環境での灰色の死の画面を削除する

イントロダクション – グレー画面が問題である理由

Lorsque le build, le layout ou le paint d'un widget échouent, Flutter appelle ErrorWidget.builder.
En debug, le framework montre un rectangle rouge détaillant la stack trace ; en release, il rend un fond gris neutre pour ne pas exposer d'informations sensibles au public (Flutter におけるエラー処理).
Disgracieux et sans texte, cet écran ne rassure pas l'utilisateur!

Flutter のエラー処理チェーンを理解する

Cette section clarifie 誰が capte l'erreur, いつ, et どのように personnaliser la réponse.

1. FlutterError.onError : 最初の連結点

  • Rôle : handler global invoqué pour toute erreur détectée sur la pile Flutter.
  • Défaut : appelle FlutterError.presentError, qui se contente d'imprimer la log.
  • Personnalisation : remplacez-le par votre fonction ; vous pouvez toujours appeler presentError pour garder la trace console (onError プロパティ - FlutterError クラス - foundation ライブラリ - Dart API).
  • Portée : erreurs synchrones du framework, y compris celles déclenchées durant setState, build, layout ou paint.

🧠 知っておくと良い – Parce qu'il s'exécute avant le rendu, onError vous permet de router l'exception vers Crashlytics tout en laissant le rendu à ErrorWidget.builder.

2. ErrorWidget.builder : 問題のあるウィジェットを置き換える画面

  • Quand est-il appelé ? Lorsqu'une exception survient pendant la phase build.
  • Comportement par défaut :
  • Personnalisation : définissez votre propre builder pour retourner un composant UX-friendly — texte clair, contraste suffisant, bouton retour.

3. Zones Dart et runZonedGuarded : capter l'asynchrone

4. PlatformDispatcher.onError : 最後のネイティブの砦

  • Contexte : une erreur peut provenir d'un plugin ou d'un canal de méthode exécuté côté moteur (C++/JNI).
  • Callback : PlatformDispatcher.instance.onError reçoit l'exception et la stack native.
  • Convention : retournez true si vous avez géré le problème ; sinon Flutter interrompra le processus (Flutter におけるエラー処理).

➡️ 時間的な順序

ステップ誰が捕捉する?エラーの例カスタマイズの可能性
1FlutterError.onErrorsetState が破棄済みのウィジェットで呼ばれるLog + trigger Crashlytics
2ErrorWidget.builderbuild() 内のゼロ除算救済画面をレンダリング
3runZonedGuardedFuture 内のタイムアウトゾーンを自動的に再実行
4PlatformDispatcher.onErrorAndroid の NullPointerExceptionネイティブフォールバック画面 の表示

Ainsi, いかなる exception ne devrait atteindre l'utilisateur sans passer par au moins un de ces crochets.

ステップバイステップのチュートリアル

1. グローバルな エラー境界 を初期化する (main.dart)

import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // 1. Capture Flutter synchrones
  FlutterError.onError = (details) {
    FlutterError.presentError(details);          // Log locale
    _report(details.exception, details.stack);   // Crashlytics / Sentry
  };

  // 2. Capture erreurs natives (plugins, channels)
  PlatformDispatcher.instance.onError = (error, stack) {
    _report(error, stack);
    return true;                                 // Erreur considérée gérée
  };

  // 3. Capture erreurs asynchrones hors pile Flutter
  runZonedGuarded(
    () => runApp(const MyApp()),
    (error, stack) => _report(error, stack),
  );
}

void _report(Object error, StackTrace? stack) {
  // TODO : intégrer FirebaseCrashlytics.instance.recordError(...)
}

Cette triple interception couvre les cas documentés par l'équipe Flutter (一般的な Flutter エラー, Flutter におけるエラー処理).

2. アクセシブルな CustomErrorScreen を作成する (WCAG 2.2)

class CustomErrorScreen extends StatelessWidget {
  final Object error;
  const CustomErrorScreen(this.error, {super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      body: Center(
        child: Semantics(
          label: 'Erreur critique',
          child: ConstrainedBox(
            constraints: const BoxConstraints(maxWidth: 320),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                const Icon(Icons.error_outline, size: 96),
                const SizedBox(height: 24),
                Text(
                  'Oups ! Un incident est survenu.',
                  style: Theme.of(context).textTheme.headlineSmall,
                  textAlign: TextAlign.center,
                ),
                const SizedBox(height: 12),
                Text(
                  'Notre équipe a été notifiée. Vous pouvez relancer l'application.',
                  textAlign: TextAlign.center,
                ),
                const SizedBox(height: 32),
                ElevatedButton(
                  onPressed: () => runApp(const MyApp()),
                  child: const Text('Redémarrer'),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}
ErrorWidget.builder = (details) => CustomErrorScreen(details.exception);

3. Crashlytics または Sentry へのログ送信

import 'package:firebase_crashlytics/firebase_crashlytics.dart';

void _report(Object error, StackTrace? stack) {
  FirebaseCrashlytics.instance.recordError(error, stack);
  // Ou: Sentry.captureException(error, stackTrace: stack);
}

これらのプラットフォームはエラー、スタック、デバイスコンテキストをまとめ、リアルタイムでアラートを発します。

4. 単体テスト & ウィジェットテスト

testWidgets('CustomErrorScreen remplace le widget fautif', (tester) async {
  ErrorWidget.builder = (d) => CustomErrorScreen(d.exception);
  await tester.pumpWidget(const BrokenWidget());   // Simule un widget erroné
  expect(find.byType(CustomErrorScreen), findsOneWidget);
});

Le guide Flutter におけるエラー処理 détaille l'injection d'un MaterialApp.builder pour des tests plus complexes (Flutter におけるエラー処理).

結論

要点

  • Le グレー画面 masque une exception non gérée ; il nuit à la confiance et à la rétention.
  • Combinez FlutterError.onErrorErrorWidget.builderrunZonedGuardedPlatformDispatcher.onError pour capturer すべて les scénarios.
  • Affichez un CustomErrorScreen conforme WCAG, puis tracez l'incident vers Crashlytics ou Sentry.

タグ

  • フラッター

  • ios

  • アンドロイド

  • ダーツ

  • ファイアベース

  • クラッシュリティクス

  • テスト

この記事は

コメント

読み込み中...

Flutter:本番環境での灰色の死の画面を削除する | DEMILY Clément