scopo 0.2.1 copy "scopo: ^0.2.1" to clipboard
scopo: ^0.2.1 copied to clipboard

A Flutter package for managing scopes and dependency injection within the widget tree

scopo #

A Flutter package for managing scopes, dependency injection, and asynchronous initialization in your widget tree.

scopo simplifies the process of creating hierarchical scopes where dependencies can be initialized, accessed, and disposed of automatically. It handles loading states, errors, and provides a clean API for accessing dependencies and UI state.

Features #

  • Scoped Dependency Injection: easily provide and access dependencies down the widget tree.
  • Asynchronous Initialization: built-in support for async dependency initialization with progress reporting.
  • Lifecycle Management: automatically disposes of dependencies when the scope is closed.
  • Error Handling: graceful handling of initialization errors.
  • Separation of Concerns: clearly separates Dependencies (ScopeDeps), UI Logic (ScopeContent), and Wiring (Scope).

Installation #

Add scopo to your pubspec.yaml:

dependencies:
  scopo: ^0.2.1

Or run:

flutter pub add scopo

Usage #

Using scopo involves three main steps: defining your dependencies, defining your content (UI/Logic), and creating the Scope widget.

1. Define Dependencies #

Create a class that implements ScopeDeps. This class holds your services, repositories, etc. Implement the static init method to initialize them.

import 'package:scopo/scopo.dart';

class MyFeatureDeps implements ScopeDeps {
  final ApiService apiService;
  final Database db;

  MyFeatureDeps({required this.apiService, required this.db});

  // The initialization logic.
  static Stream<ScopeInitState<double, MyFeatureDeps>> init() async* {
    ApiService? apiService;
    Database? db;
    var _isInitialized = false;

    try {
      // 1. Initialize API Service.
      apiService = ApiService();
      await apiService.init();
      yield ScopeProgress(0.5); // Report progress 50%

      // 2. Initialize Database.
      final db = Database();
      await db.open();
      yield ScopeProgress(1.0); // Report progress 100%

      // 3. Ready!
      yield ScopeReady(
        MyFeatureDeps(
          apiService: apiService,
          db: db,
        ),
      );

      _isInitialized = true;
    } finally {
      // Clean up in case of error or cancellation.
      if (!_isInitialized) {
        await db?.close();
        await apiService?.dispose();
      }
    }
  }

  @override
  Future<void> dispose() async {
    // Clean up.
    await db.close();
    await apiService.dispose();
  }
}

2. Define Content #

Create a class that extends ScopeContent. This is where your UI logic and state reside. It has access to the dependencies.

class MyFeatureContent extends ScopeContent<MyFeatureScope, MyFeatureDeps, MyFeatureContent> {
  @override
  void initState() {
    super.initState();
    ...
  }

  // Access dependencies via `deps`.
  void fetchData() {
    deps.apiService.fetch();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('My Feature')),
      body: Center(child: Text('Feature Content')),
    );
  }
}

3. Define the Scope #

Create a widget that extends Scope. This connects the dependencies and the content.

class MyFeatureScope extends Scope<MyFeatureScope, MyFeatureDeps, MyFeatureContent> {
  const MyFeatureScope({super.key}) : super(init: MyFeatureDeps.init);

  // Boilerplate for easy access.
  static MyFeatureContent of(BuildContext context) =>
      Scope.of<MyFeatureScope, MyFeatureDeps, MyFeatureContent>(context);

  @override
  MyFeatureContent createContent() => MyFeatureContent();

  // Loading screen.
  @override
  Widget onInit(Object? progress) {
    return Scaffold(
      body: Center(
        child: CircularProgressIndicator(
          value: progress as double?,
        ),
      ),
    );
  }

  // Error screen.
  @override
  Widget onError(Object error, StackTrace stackTrace) {
    return Scaffold(
      body: Center(
        child: Text('Error: $error'),
      ),
    );
  }

  @override
  bool updateParamsShouldNotify(MyFeatureScope oldWidget) => false;
}

4. Use it #

Simply wrap your widget tree (or part of it) with your Scope.

void main() {
  runApp(
    const MaterialApp(
      home: MyFeatureScope(),
    ),
  );
}

Accessing Dependencies #

You can access the scope and its dependencies from any child widget:

// Get the content (and dependencies via content.deps)
final content = Scope.of<MyFeatureScope, MyFeatureDeps, MyFeatureContent>(context);

// Or if you added the helper method in your `Scope` class:
final deps = MyFeatureScope.of(context).deps;

Examples #

Check out the example directory for more comprehensive examples:

  • minimal: A simple counter app demonstrating basic usage.
  • scopo_demo: A more complex app with nested scopes, error simulation, and custom progress indicators.
0
likes
0
points
307
downloads

Publisher

verified publisheryet-another.dev

Weekly Downloads

A Flutter package for managing scopes and dependency injection within the widget tree

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter

More

Packages that depend on scopo