Skip to content

Accessing get_it Features

This guide shows how to access get_it features from within watch_it widgets. For detailed explanations of each get_it feature, see the get_it documentation.

Scopes with pushScope

get_it scopes create temporary registrations that are automatically cleaned up. Perfect for screen-specific state. See get_it Scopes for details.

pushScope() - Automatic Scope Management

pushScope() creates a scope when the widget mounts and automatically cleans it up on dispose:

dart
class UserProfileScreen extends WatchingWidget {
  @override
  Widget build(BuildContext context) {
    // Create scope on first build
    pushScope(
      init: (getIt) {
        // Register screen-specific dependencies
        getIt.registerLazySingleton<ProfileManager>(
          () => ProfileManager(userId: '123'),
        );
      },
      dispose: () {
        // Optional cleanUp when the scope is popped when the widget gets disposed
        print('Profile screen scope disposed');
      },
    );

    // Use scoped dependencies
    final profile = watchValue((ProfileManager m) => m.profile);

    return YourUI();
  }
}

What happens:

  1. Widget builds first time → Scope is pushed, init callback runs
  2. Dependencies registered in new scope
  3. Widget can watch scoped dependencies
  4. Widget disposes → Scope is automatically popped, dispose callback runs

Use Case: Screen-Specific State

dart
class ProductDetailScreen extends WatchingWidget {
  final String productId;

  ProductDetailScreen({required this.productId});

  @override
  Widget build(BuildContext context) {
    pushScope(
      init: (getIt) {
        // Register screen-specific manager
        getIt.registerLazySingleton<ProductDetailManager>(
          () => ProductDetailManager(productId: productId),
        );
      },
    );

    final product = watchValue((ProductDetailManager m) => m.product);
    final isLoading = watchValue((ProductDetailManager m) => m.isLoading);

    if (isLoading) return CircularProgressIndicator();
    return ProductDetailView(product: product);
  }
}

Named Instances

Watch specific named instances from get_it. See get_it Named Instances for registration details.

Watching Named Instances

dart
class ApiMonitor extends WatchingWidget {
  @override
  Widget build(BuildContext context) {
    // Watch specific named instances
    final prodApi = watchValue(
      (ApiClientExtended api) => api.requestCount,
      instanceName: 'production',
    );

    final stagingApi = watchValue(
      (ApiClientExtended api) => api.requestCount,
      instanceName: 'staging',
    );

    return Column(
      children: [
        Text('Production: $prodApi requests'),
        Text('Staging: $stagingApi requests'),
      ],
    );
  }
}

Use cases:

  • Multiple configurations (dev/prod)
  • Feature flags
  • A/B testing variants

Async Initialization

Handle complex initialization where async dependencies must be ready before the app starts. See get_it Async Objects for registration details.

isReady - Single Dependency

Check if a specific async dependency is ready:

dart
void setupDependenciesAsync() async {
  // Register async singleton
  di.registerSingletonAsync<Database>(
    () async {
      final db = Database();
      await db.initialize();
      return db;
    },
  );
}

class App extends WatchingWidget {
  @override
  Widget build(BuildContext context) {
    // Check if ready
    final ready = isReady<Database>();

    if (!ready) {
      return SplashScreen();
    }

    return MainApp();
  }
}

allReady - Multiple Dependencies

Wait for all async dependencies to complete:

dart
void setupMultipleDependencies() async {
  di.registerSingletonAsync<Database>(() async {
    final db = Database();
    await db.initialize();
    return db;
  });

  di.registerSingletonAsync<ConfigService>(() async {
    final config = ConfigService();
    await config.loadFromFile();
    return config;
  });

  di.registerSingletonAsync<AuthServiceAdvanced>(
    () async {
      final auth = AuthServiceAdvanced();
      await auth.initialize();
      return auth;
    },
    dependsOn: [Database], // Waits for Database first
  );
}

class AppAllReady extends WatchingWidget {
  @override
  Widget build(BuildContext context) {
    // Wait for all async singletons
    final ready = allReady(
      timeout: Duration(seconds: 30),
      onError: (context, error) {
        // Handle timeout or initialization errors
        // Without onError, exceptions would be thrown!
        showErrorDialog(context, error);
      },
    );

    if (!ready) {
      return Scaffold(
        body: Center(
          child: CircularProgressIndicator(),
        ),
      );
    }

    return MainApp();
  }
}

Error Handling

When using timeout, allReady() can throw WaitingTimeOutException if dependencies don't initialize in time. Additionally, if any async singleton's init function throws an exception, it propagates through allReady().

Use the onError callback to handle errors gracefully - otherwise exceptions will be thrown during widget build.

Alternative: watchFuture for Full Error Control

If you expect initialization errors and need full control via AsyncSnapshot, use watchFuture with di.allReady() directly:

dart
class AppWithFullErrorHandling extends WatchingWidget {
  @override
  Widget build(BuildContext context) {
    // watchFuture gives you an AsyncSnapshot for full error control
    final snapshot = watchFuture<GetIt, void>(
      (getIt) => getIt.allReady(timeout: Duration(seconds: 30)),
      target: di,
      initialValue: null,
    );

    if (snapshot.hasError) {
      // Handle initialization errors (timeout or factory exceptions)
      return ErrorScreen(error: snapshot.error);
    }

    if (snapshot.connectionState != ConnectionState.done) {
      return SplashScreen();
    }

    return MainApp();
  }
}

When to use which:

  • allReady() with onError — simple cases, just need ready/not ready state
  • watchFuture(di.allReady()) — when you need the full AsyncSnapshot (hasError, error, connectionState)

Watching Initialization Progress

dart
class InitializationScreen extends WatchingWidget {
  @override
  Widget build(BuildContext context) {
    final dbReady = isReady<Database>();
    final configReady = isReady<ConfigService>();
    final authReady = isReady<AuthServiceAdvanced>();

    final progress =
        [dbReady, configReady, authReady].where((ready) => ready).length / 3;

    if (dbReady && configReady && authReady) {
      // All ready, navigate to main app
      callOnceAfterThisBuild((context) {
        Navigator.of(context).pushReplacement(
          MaterialPageRoute(builder: (_) => MainApp()),
        );
      });
    }

    return Column(
      children: [
        LinearProgressIndicator(value: progress),
        Text('Initializing... ${(progress * 100).toInt()}%'),
        if (dbReady) Text('✓ Database ready'),
        if (configReady) Text('✓ Configuration loaded'),
        if (authReady) Text('✓ Authentication ready'),
      ],
    );
  }
}

See Also

Released under the MIT License.