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
presentErrorpour garder la trace console (onError プロパティ - FlutterError クラス - foundation ライブラリ - Dart API). - Portée : erreurs synchrones du framework, y compris celles déclenchées durant
setState,build,layoutoupaint.
🧠 知っておくと良い – Parce qu'il s'exécute avant le rendu,
onErrorvous 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 :
- Debug/Profile : rectangle rouge + message.
- Release : fond gris uni (alias 灰色の死亡画面) (Flutter におけるエラー処理).
- 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
- Problème : les futures détachés du cycle Flutter n'arrivent pas à
onError. - Solution : entourez
runAppd'unrunZonedGuarded; toute erreur asynchrone dans cette zone est redirigée vers votre callbackonErrorpersonnalisé (runZonedGuarded 関数 - dart:async ライブラリ - Dart API - Flutter, ゾーン | Dart). - Bonus : en production, cela évite un crash d'Isolate ou le blocage complet de l'application.
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.onErrorreçoit l'exception et la stack native. - Convention : retournez
truesi vous avez géré le problème ; sinon Flutter interrompra le processus (Flutter におけるエラー処理).
➡️ 時間的な順序
| ステップ | 誰が捕捉する? | エラーの例 | カスタマイズの可能性 |
|---|---|---|---|
| 1 | FlutterError.onError | setState が破棄済みのウィジェットで呼ばれる | Log + trigger Crashlytics |
| 2 | ErrorWidget.builder | build() 内のゼロ除算 | 救済画面をレンダリング |
| 3 | runZonedGuarded | Future 内のタイムアウト | ゾーンを自動的に再実行 |
| 4 | PlatformDispatcher.onError | Android の 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'),
),
],
),
),
),
),
);
}
}
- コントラスト 最低 4.5:1 (WCAG 2.2 SC 1.4.3).
- 説明的なメッセージ : 成功基準 3.3.1「Error Identification」を満たしています(成功基準 3.3.1 の理解: Error Identification | WAI - W3C)。
- 有効化 :
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.onError→ErrorWidget.builder→runZonedGuarded→PlatformDispatcher.onErrorpour capturer すべて les scénarios. - Affichez un
CustomErrorScreenconforme WCAG, puis tracez l'incident vers Crashlytics ou Sentry.
コメント
読み込み中...