Flutter: eliminar la pantalla gris de la muerte en producción

Flutter: eliminar la pantalla gris de la muerte en producción

Introducción – por qué la pantalla gris es un problema

Cuando el build, el layout o el paint de un widget fallan, Flutter llama a ErrorWidget.builder.
En modo debug, el framework muestra un rectángulo rojo detallando la traza de la pila ; en release, muestra un fondo gris neutro para no exponer información sensible al público (Manejo de errores en Flutter).
Feo y sin texto, ¡esa pantalla no tranquiliza al usuario!

Comprender la cadena de gestión de errores de Flutter

Esta sección aclara quién captura el error, cuándo, y cómo personalizar la respuesta.

1. FlutterError.onError : el primer eslabón

  • Rol : manejador global invocado para cualquier error detectado en la pila de Flutter.
  • Por defecto : llama a FlutterError.presentError, que se limita a imprimir el registro.
  • Personalización : reemplácelo por su función ; aún puede llamar a presentError para mantener la traza en la consola (propiedad onError - clase FlutterError - biblioteca foundation - API de Dart).
  • Alcance : errores síncronos del framework, incluyendo aquellos desencadenados durante setState, build, layout o paint.

🧠 Buen dato – Como se ejecuta antes del renderizado, onError le permite enrutar la excepción a Crashlytics mientras deja el renderizado a ErrorWidget.builder.

2. ErrorWidget.builder : la pantalla que reemplaza al widget defectuoso

  • ¿Cuándo se llama ? Cuando ocurre una excepción durante la fase build.
  • Comportamiento por defecto :
    • Debug/Profile : rectángulo rojo + mensaje.
    • Release : fondo gris uniforme (también conocido como grey screen of death) (Manejo de errores en Flutter).
  • Personalización : defina su propio builder para devolver un componente amigable para UX — texto claro, suficiente contraste, botón de regreso.

3. Zonas Dart y runZonedGuarded : capturar lo asíncrono

4. PlatformDispatcher.onError : último recurso nativo

  • Contexto : un error puede provenir de un plugin o de un canal de método ejecutado del lado del motor (C++/JNI).
  • Callback : PlatformDispatcher.instance.onError recibe la excepción y la pila nativa.
  • Convención : devuelva true si ha manejado el problema ; de lo contrario Flutter terminará el proceso (Manejo de errores en Flutter).

➡️ Secuencia temporal

Paso¿Quién captura ?Ejemplo de errorPosibilidades de personalización
1FlutterError.onErrorsetState en widget desmontadoRegistro + notificar Crashlytics
2ErrorWidget.builderDivisión por cero en build()Renderizado de una pantalla de emergencia
3runZonedGuardedTiempo de espera en un FutureReinicio automático de la zona
4PlatformDispatcher.onErrorNullPointerException en AndroidMostrar una pantalla nativa de respaldo

Así, ninguna excepción debería llegar al usuario sin pasar por al menos uno de estos ganchos.

Tutorial paso a paso

1. Inicializar un Error Boundary global (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 (Errores comunes de Flutter, Manejo de errores en Flutter).

2. Crear un CustomErrorScreen accesible (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. Registrar en Crashlytics o 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);
}

Estas plataformas agrupan el error, la pila, el contexto del dispositivo y disparan alertas en tiempo real.

4. Pruebas unitarias y pruebas de widgets

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);
});

La guía Manejo de errores en Flutter detalla la inyección de un MaterialApp.builder para pruebas más complejas (Manejo de errores en Flutter).

Conclusión

Puntos clave

  • La pantalla gris oculta una excepción no gestionada ; perjudica la confianza y la retención.
  • Combine FlutterError.onErrorErrorWidget.builderrunZonedGuardedPlatformDispatcher.onError para capturar todos los escenarios.
  • Muestre un CustomErrorScreen conforme a WCAG, luego registre el incidente en Crashlytics o Sentry.

Etiquetas

  • flutter

  • ios

  • android

  • dart

  • firebase

  • crashlytics

  • pruebas

Este artículo fue publicado el

Comentarios

Cargando...

Flutter: eliminar la pantalla gris de la muerte en producción | DEMILY Clément