El autor está disponible para consultoría y trabajo por contratoAgenda una llamada →
Skip to content

Flutter Widget Previews ​

Esta guía te muestra cómo usar get_it con el widget previewer de Flutter.

El Desafío ​

Cuando usas la anotación @Preview, Flutter renderiza tu widget sin llamar a main() o ejecutar el código de inicio de tu app. Esto significa:

  • get_it no ha sido inicializado
  • No se han registrado servicios
  • Los widgets que llaman a GetIt.I<SomeService>() lanzarán errores

Necesitas manejar la inicialización de get_it dentro del preview mismo.

Dos Enfoques ​

Hay dos formas de inicializar get_it para previews, cada una con diferentes compromisos:

  1. Registro Directo - Patrón simple de verificar y registrar
  2. Widget Wrapper - Wrapper reutilizable con limpieza automática

Elige según tus necesidades:

EnfoqueMejor ParaProsContras
Registro DirectoPreviews simples, de una sola vezControl máximo, código mínimoSin limpieza automática, guards manuales
Widget WrapperConfiguraciones reutilizables, múltiples previewsLimpieza automática, principio DRYConfiguración ligeramente mayor

Enfoque 1: Registro Directo ​

El enfoque más simple es verificar si los servicios están registrados y registrarlos si no lo están.

Cómo Funciona ​

Flutter puede llamar a tu función de preview múltiples veces (en hot reload, etc.), así que proteges contra registro doble usando isRegistered():

dart
@Preview()
Widget userProfilePreview() {
  // The preview function may be called multiple times during hot reload,
  // so guard against double registration by checking the last service
  if (!getIt.isRegistered<MockApiClient>()) {
    getIt.registerSingleton<MockUserService>(MockUserService());
    getIt.registerLazySingleton<MockApiClient>(() => MockApiClient());
  }

  return const MaterialApp(
    home: Scaffold(
      body: UserProfileWidget(),
    ),
  );
}

Cuándo Usar ​

  • Previews de una sola vez con dependencias únicas
  • Prototipado rápido donde quieres resultados inmediatos
  • Control máximo sobre el timing de inicialización

Pros y Contras ​

Pros:

  • Código mínimo, fácil de entender
  • Control completo sobre el orden de registro
  • No se necesitan widgets adicionales

Contras:

  • Verificaciones de guard manuales para cada servicio
  • Sin limpieza automática (permanece en get_it hasta reset manual)
  • Duplicación de código si múltiples previews necesitan la misma configuración

Enfoque 2: Widget Wrapper ​

Para mejor organización y reutilización, crea un widget wrapper que maneje inicialización y limpieza automáticamente.

El Widget Wrapper ​

Primero, crea un widget wrapper reutilizable:

dart
/// A wrapper widget for Flutter widget previews that initializes GetIt.
///
/// This widget handles GetIt setup in initState and cleanup via reset()
/// in dispose, making it perfect for preview scenarios where widgets are
/// rendered in isolation.
class GetItPreviewWrapper extends StatefulWidget {
  const GetItPreviewWrapper({
    super.key,
    required this.init,
    required this.child,
  });

  /// The child widget to render after GetIt is initialized
  final Widget child;

  /// Initialization function that registers dependencies in GetIt
  final void Function(GetIt getIt) init;

  @override
  State<GetItPreviewWrapper> createState() => _GetItPreviewWrapperState();
}

class _GetItPreviewWrapperState extends State<GetItPreviewWrapper> {
  @override
  void initState() {
    super.initState();
    // Initialize GetIt with preview dependencies
    widget.init(GetIt.instance);
  }

  @override
  void dispose() {
    // Clean up all GetIt registrations when preview is disposed
    GetIt.instance.reset();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return widget.child;
  }
}

Usando el Wrapper ​

Usa el wrapper con el parámetro wrapper de la anotación @Preview:

dart
// Top-level wrapper function for @Preview annotation
Widget myPreviewWrapper(Widget child) {
  return GetItPreviewWrapper(
    init: (getIt) {
      // Register all preview dependencies here
      getIt.registerLazySingleton<MockApiClient>(() => MockApiClient());
      getIt.registerSingleton<MockUserService>(MockUserService());
      getIt.registerFactory<MockAuthService>(() => MockAuthService());
    },
    child: child,
  );
}

// Use the wrapper in your preview
@Preview(name: 'Dashboard Widget', wrapper: myPreviewWrapper)
Widget dashboardPreview() => const MaterialApp(
      home: Scaffold(
        body: DashboardWidget(),
      ),
    );

Cuándo Usar ​

  • Múltiples previews que comparten las mismas dependencias
  • Configuraciones reutilizables a través de diferentes previews de widgets
  • Limpieza automática vía reset() en dispose
  • Código más limpio con separación de responsabilidades

Pros y Contras ​

Pros:

  • Limpieza automática cuando el preview es dispuesto
  • Reutilizable a través de múltiples previews
  • Código de preview más limpio (la configuración está separada)
  • Fácil de crear múltiples configuraciones

Contras:

  • Requiere definición de widget wrapper separada
  • La función wrapper debe ser de nivel superior o estática
  • Configuración inicial ligeramente mayor

Probando Diferentes Escenarios ​

Un uso poderoso del enfoque wrapper es crear diferentes escenarios para el mismo widget:

dart
// Create different wrappers for different scenarios

// Logged in user scenario
Widget loggedInWrapper(Widget child) {
  return GetItPreviewWrapper(
    init: (getIt) {
      getIt.registerSingleton<MockAuthService>(
        MockAuthService()..isAuthenticated = true,
      );
      getIt.registerSingleton<MockUserService>(
        MockUserService()..currentUser = 'John Doe',
      );
    },
    child: child,
  );
}

// Logged out user scenario
Widget loggedOutWrapper(Widget child) {
  return GetItPreviewWrapper(
    init: (getIt) {
      getIt.registerSingleton<MockAuthService>(
        MockAuthService()..isAuthenticated = false,
      );
    },
    child: child,
  );
}

// Error state scenario
Widget errorStateWrapper(Widget child) {
  return GetItPreviewWrapper(
    init: (getIt) {
      getIt.registerSingleton<MockApiClient>(
        MockApiClient()..shouldFail = true,
      );
    },
    child: child,
  );
}

// Use different wrappers to preview different states
@Preview(name: 'Login Button - Logged In', wrapper: loggedInWrapper)
Widget loginButtonLoggedIn() => const LoginButtonWidget();

@Preview(name: 'Login Button - Logged Out', wrapper: loggedOutWrapper)
Widget loginButtonLoggedOut() => const LoginButtonWidget();

@Preview(name: 'Dashboard - Error State', wrapper: errorStateWrapper)
Widget dashboardError() => const DashboardWidget();

Este patrón es excelente para:

  • Probar casos extremos (estados de error, datos vacíos, carga)
  • Diferentes estados de usuario (loggeado, desloggeado, invitado)
  • Pruebas de accesibilidad (diferentes tamaños de fuente, temas)
  • Diseño responsivo (diferentes tamaños de pantalla con parámetro size)

Ejemplo Completo ​

Mira la app de ejemplo de get_it para un ejemplo completo funcionando mostrando ambos enfoques.

El ejemplo incluye:

  • preview() - Enfoque de registro directo
  • previewWithWrapper() - Enfoque wrapper
  • Implementación de GetItPreviewWrapper en preview_wrapper.dart

Consejos y Mejores Prácticas ​

Usando Servicios Reales vs Mock ​

Uno de los beneficios clave de get_it es que puedes conectar tus widgets a servicios reales en previews, permitiéndote ver tus widgets con datos y comportamiento reales. Solo necesitas asegurar inicialización apropiada:

dart
// Servicios reales - perfectamente válido si se inicializa apropiadamente
Widget realServicesWrapper(Widget child) {
  return GetItPreviewWrapper(
    init: (getIt) {
      getIt.registerSingleton<ApiClient>(ApiClient(baseUrl: 'https://api.example.com'));
      getIt.registerSingleton<AuthService>(AuthService());
      getIt.registerSingleton<DatabaseService>(DatabaseService());
    },
    child: child,
  );
}

Sin embargo, se recomiendan servicios mock cuando quieres:

  • Pruebas aisladas de estados específicos de UI
  • Renderizado rápido sin retrasos de red/base de datos
  • Escenarios controlados (estados de error, casos extremos, datos vacíos)
dart
// Servicios mock - excelente para probar escenarios específicos
getIt.registerSingleton<ApiClient>(MockApiClient()); // Respuestas instantáneas

Elige según los objetivos de tu preview: servicios reales para previews estilo integración, mocks para pruebas aisladas de estado de UI.

Inicialización Async ​

La inicialización async funciona normalmente en previews. Las funciones factory async se llaman solo una vez, igual que en tu app regular. La clave es usar allReady() o isReady<T>() en tus widgets para esperar la inicialización:

dart
Widget asyncPreviewWrapper(Widget child) {
  return GetItPreviewWrapper(
    init: (getIt) {
      // Los registros async funcionan perfectamente - factory llamada una vez
      getIt.registerSingletonAsync<ApiService>(
        () async => ApiService().initialize(),
      );
      getIt.registerSingletonAsync<DatabaseService>(
        () async => DatabaseService().connect(),
      );
    },
    child: child,
  );
}

// En tu widget, espera a que los servicios estén listos
class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: getIt.allReady(), // Espera a que todos los async singletons estén listos
      builder: (context, snapshot) {
        if (snapshot.hasData) {
          // Servicios están listos, úsalos
          final api = getIt<ApiService>();
          return Text(api.data);
        }
        return CircularProgressIndicator(); // Muestra carga
      },
    );
  }
}

Nota: El entorno de preview está basado en web, así que I/O de archivos (dart:io) y plugins nativos no funcionarán, pero llamadas de red y la mayoría de operaciones async funcionan bien.

Crea Wrappers Reutilizables ​

Si tienes configuraciones comunes, crea wrappers con nombre:

dart
// Define una vez
Widget basicAppWrapper(Widget child) => GetItPreviewWrapper(
  init: (getIt) {
    getIt.registerSingleton<ApiClient>(MockApiClient());
    getIt.registerSingleton<AuthService>(MockAuthService());
  },
  child: child,
);

// Reutiliza en todas partes
@Preview(name: 'Widget 1', wrapper: basicAppWrapper)
Widget widget1Preview() => const Widget1();

@Preview(name: 'Widget 2', wrapper: basicAppWrapper)
Widget widget2Preview() => const Widget2();

Combina con Otros Parámetros de Preview ​

Puedes usar wrappers de get_it junto con otras características de preview:

dart
@Preview(
  name: 'Responsive Dashboard',
  wrapper: myPreviewWrapper,
  size: Size(375, 812), // Tamaño de iPhone 11 Pro
  textScaleFactor: 1.3,  // Prueba de accesibilidad
)
Widget dashboardPreview() => const DashboardWidget();

Solución de Problemas ​

"get_it: Object/factory with type X is not registered" ​

Tu función de preview se está llamando antes de que get_it sea inicializado. Usa uno de los dos enfoques anteriores para registrar servicios antes de accederlos.

Preview no se actualiza en hot reload ​

El dispose() del wrapper podría no estar siendo llamado. Intenta detener y reiniciar el preview, o usa el enfoque de registro directo con verificaciones isRegistered().

Servicios persistiendo entre previews ​

Si usas registro directo sin limpieza, los servicios permanecen en get_it. Ya sea:

  • Usa el enfoque wrapper (automático reset() en dispose)
  • Llama manualmente a await GetIt.I.reset() cuando sea necesario
  • Usa instancias con nombre separadas para diferentes previews

Aprende Más ​

Próximos Pasos ​

  • Prueba ambos enfoques en tu proyecto
  • Crea funciones wrapper reutilizables para escenarios comunes
  • Explora combinar previews con diferentes temas y tamaños
  • Revisa el ejemplo completo en el repositorio de get_it

Publicado bajo la Licencia MIT. Creado por Thomas Burkhart.