dart_scope 0.1.0-beta.4 copy "dart_scope: ^0.1.0-beta.4" to clipboard
dart_scope: ^0.1.0-beta.4 copied to clipboard

A declarative dependency injection library which use dart syntax and flutter style

example/main.dart

import 'package:dart_scope/dart_scope.dart';

class Repository {
  // ...implementations  
}

class AppNotifier {
  AppNotifier({
    required this.repository,
  });
  final Repository repository;
  // ...implementations
  void dispose() {}
}

/// Example simulates:
/// ```dart
/// void rootScope() {
/// 
///   final Repository repository = Repository();
///   final AppNotifier appNotifier = AppNotifier(
///     repository: repository,
///   );
///
///   final myRepository = repository;
///   final myAppNotifier = appNotifier;
/// }
/// ```
Future<void> scopeRootExample() async {
  final rootScope = await Scope.root([
    Final<Repository>(name: 'repository', equal: (scope) => Repository()),
    Final<AppNotifier>(name: 'appNotifier', equal: (scope) => AppNotifier(
      repository: scope.get<Repository>(name: 'repository'),
    )),
  ]);

  final myRepository = rootScope.get<Repository>(name: 'repository');
  final myAppNotifier = rootScope.get<AppNotifier>(name: 'appNotifier');

  print('myRepository: $myRepository');
  print('myAppNotifier: $myAppNotifier');
}

/// Example simulates:
/// ```dart
/// void rootScope() {
/// 
///   final Repository repository1 = Repository();
///   final Repository repository2 = Repository();
///   final Repository repository3 = Repository();
/// 
///   final myRepository1 = repository1;
///   final myRepository2 = repository2;
///   final myRepository3 = repository3;
/// }
/// ```
Future<void> multipleNamesExample() async {
  final rootScope = await Scope.root([
    Final<Repository>(name: 'repository1', equal: (scope) => Repository()),
    Final<Repository>(name: 'repository2', equal: (scope) => Repository()),
    Final<Repository>(name: 'repository3', equal: (scope) => Repository()),
  ]);

  final myRepository1 = rootScope.get<Repository>(name: 'repository1');
  final myRepository2 = rootScope.get<Repository>(name: 'repository2');
  final myRepository3 = rootScope.get<Repository>(name: 'repository3');

  print('myRepository1: $myRepository1');
  print('myRepository2: $myRepository2');
  print('myRepository3: $myRepository3');
}

/// Name can be private, so instance can only be resolved 
/// in current library (mostly current file)
final _privateName = Object();

Future<void> privateNameExample() async {
  final rootScope = await Scope.root([
    Final<Repository>(name: _privateName, equal: (scope) => Repository()),
  ]);

  final myRepository = rootScope.get<Repository>(name: _privateName);

  print('myRepository: $myRepository');
}

/// Name can be omitted, in this case `null` is used as name
Future<void> omitNameExample() async {
  final rootScope = await Scope.root([
    Final<Repository>(equal: (scope) => Repository()),
    Final<AppNotifier>(equal: (scope) => AppNotifier(
      repository: scope.get<Repository>(),
    )),
  ]);

  final myRepository = rootScope.get<Repository>();
  final myAppNotifier = rootScope.get<AppNotifier>();

  print('myRepository: $myRepository');
  print('myAppNotifier: $myAppNotifier');
}

// simulate async resolve instance like `SharedPreferences.getInstance()`
Future<Repository> createRepositoryAsync() async {
  await Future<void>.delayed(Duration(seconds: 1));
  return Repository();
}

/// Example simulates async setup:
/// ```dart
/// void rootScope() async {
///   final Repository repository = await createRepositoryAsync();
///   final AppNotifier appNotifier = AppNotifier(
///     repository: repository,
///   );
///
///   final myRepository = repository;
///   final myAppNotifier = appNotifier;
/// }
/// ```
Future<void> scopeRootAsyncExample() async {
  final rootScope = await Scope.root([
    AsyncFinal<Repository>(equal: (scope) async {
      return await createRepositoryAsync();
    }),
    Final<AppNotifier>(equal: (scope) => AppNotifier(
      repository: scope.get<Repository>(),
    )),
  ]);

  final myRepository = rootScope.get<Repository>();
  final myAppNotifier = rootScope.get<AppNotifier>();

  print('myRepository: $myRepository');
  print('myAppNotifier: $myAppNotifier');
}


class AddTodoNotifier {}

/// Example simulates:
/// ```dart
/// void rootScope() {
///   final Repository repository = Repository();
///   final AppNotifier appNotifier = AppNotifier(
///     repository: repository,
///   ); 
/// 
///   void childScope() {
///     final AddTodoNotifier addTodoNotifier = AddTodoNotifier();
/// 
///     final myRepository = repository;
///     final myAppNotifier = appNotifier;
///     final myAddTodoNotifier = addTodoNotifier;
///   }
/// }
/// ```
Future<void> scopePushExample() async {
  final rootScope = await Scope.root([
    Final<Repository>(equal: (scope) => Repository()),
    Final<AppNotifier>(equal: (scope) => AppNotifier(
      repository: scope.get<Repository>(),
    )),
  ]);

  final childScope = await rootScope.push([
    Final<AddTodoNotifier>(equal: (scope) => AddTodoNotifier()),
  ]);

  final myRepository = childScope.get<Repository>();
  final myAppNotifier = childScope.get<AppNotifier>();
  final myAddTodoNotifier = childScope.get<AddTodoNotifier>();

  print('myRepository: $myRepository');
  print('myAppNotifier: $myAppNotifier');
  print('myAddTodoNotifier: $myAddTodoNotifier');
}

/// Usage of `scope.has<T>()`
Future<void> scopeHasExample() async {
  final rootScope = await Scope.root([
    Final<Repository>(equal: (scope) => Repository()),
    Final<AppNotifier>(equal: (scope) => AppNotifier(
      repository: scope.get<Repository>(),
    )),
  ]);

  final childScope = await rootScope.push([
    Final<AddTodoNotifier>(equal: (scope) => AddTodoNotifier()),
  ]);

  print(rootScope.has<Repository>());           // true
  print(rootScope.has<AppNotifier>());          // true
  print(rootScope.has<AddTodoNotifier>());      // false

  print(childScope.has<Repository>());          // true
  print(childScope.has<AppNotifier>());         // true
  print(childScope.has<AddTodoNotifier>());     // true
}

/// Usage of `scope.getOrNull<T>()`
Future<void> scopeGetOrNullExample() async {
  final rootScope = await Scope.root([
    Final<Repository>(equal: (scope) => Repository()),
    Final<AppNotifier>(equal: (scope) => AppNotifier(
      repository: scope.get<Repository>(),
    )),
  ]);

  final childScope = await rootScope.push([
    Final<AddTodoNotifier>(equal: (scope) => AddTodoNotifier()),
  ]);

  print(rootScope.getOrNull<Repository>());           // Instance of 'Repository'
  print(rootScope.getOrNull<AppNotifier>());          // Instance of 'AppNotifier'
  print(rootScope.getOrNull<AddTodoNotifier>());      // null

  print(childScope.getOrNull<Repository>());          // Instance of 'Repository'
  print(childScope.getOrNull<AppNotifier>());         // Instance of 'AppNotifier'
  print(childScope.getOrNull<AddTodoNotifier>());     // Instance of 'AddTodoNotifier'
}

// Example for `scope.dispose()`
Future<void> scopeDisposeExample() async {
  final rootScope = await Scope.root([
    Final<Repository>(equal: (scope) => Repository()),
    Final<AppNotifier>(
      equal: (scope) => AppNotifier(
        repository: scope.get<Repository>(),
      ),
      // register dispose instance logic
      dispose: (appNotifier) => appNotifier.dispose(),
    ),
  ]);

  // dispose scope, will also dispose `appNotifier`
  rootScope.dispose();
} 

/// Instance is assigned lazily by default, 
/// set `lazy` to false, so it will be assigned immediately
Future<void> nonLazyFinalExample() async {
  final rootScope = await Scope.root([
    Final<Repository>(
      equal: (scope) => Repository(),
      lazy: false // set to false
    ),
    Final<AppNotifier>(
      equal: (scope) => AppNotifier(
        repository: scope.get<Repository>(),
      ),
      lazy: false // set to false
    ),
  ]);

  final myRepository = rootScope.get<Repository>();
  final myAppNotifier = rootScope.get<AppNotifier>();

  print('myRepository: $myRepository');
  print('myAppNotifier: $myAppNotifier');
}

/// Using inline `Configurable` to setup scope
/// in a customizable way
Future<void> configurableInlineExample() async {
  final rootScope = await Scope.root([
    Configurable((scope) {
      // build dependency graph
      late final Repository repository = Repository();
      late final AppNotifier appNotifier = AppNotifier(
        repository: repository,
      );
      // expose instances in current scope
      scope.expose<Repository>(expose: () => repository);
      scope.expose<AppNotifier>(expose: () => appNotifier);
      // register dispose logic
      scope.addDispose(() {
        appNotifier.dispose();
      });
      // done
    }),
  ]);

  final myRepository = rootScope.get<Repository>();
  final myAppNotifier = rootScope.get<AppNotifier>();

  print('myRepository: $myRepository');
  print('myAppNotifier: $myAppNotifier');
}

/// Split high level configuration into low level `Configurable`
/// which can be reused and composed. This is where `Final`
/// comes from, and how it works:
class MyFinal<T> implements Configurable {

  MyFinal({
    this.name,
    required this.equal,
    this.dispose,
    this.lazy = true,
  });

  final Object? name;
  final Equal<T> equal;
  final DisposeValue<T>? dispose;
  final bool lazy;

  @override
  FutureOr<void> configure(ConfigurableScope scope) {

    final Getter<T> getValue;
    if (lazy) {
      late final instance = equal(scope);
      getValue = () => instance;
    } else {
      final instance = equal(scope);
      getValue = () => instance;
    }

    scope.expose(name: name, expose: getValue);
    
    if (dispose != null) {
      scope.addDispose(() {
        final instance = getValue();
        dispose!(instance);
      });
    }
  }
}

Future<void> configurableExample() async {
  final rootScope = await Scope.root([
    MyFinal<Repository>(
      name: 'repository',
      equal: (scope) => Repository(),
      lazy: false,
    ),
    MyFinal<AppNotifier>(
      name: 'appNotifier',
      equal: (scope) => AppNotifier(
        repository: scope.get<Repository>(name: 'repository'),
      ),
      lazy: false,
      dispose: (appNotifier) => appNotifier.dispose(),
    ),
  ]);

  final myRepository = rootScope.get<Repository>(name: 'repository');
  final myAppNotifier = rootScope.get<AppNotifier>(name: 'appNotifier');

  print('myRepository: $myRepository');
  print('myAppNotifier: $myAppNotifier');
}

/// High level configuration is often combined/composed with 
/// low level configurations:
class AppConfigurables extends ConfigurableCombine {

  const AppConfigurables({
    this.repositoryName,
    this.appNotifierName,
    this.lazy = true,
    this.dispose = true,
  });

  final Object? repositoryName;
  final Object? appNotifierName;
  final bool lazy;
  final bool dispose;

  @override
  List<Configurable> combine() {
    return [
      MyFinal<Repository>(
        name: repositoryName,
        equal: (scope) => Repository(),
        lazy: lazy,
      ),
      MyFinal<AppNotifier>(
        name: appNotifierName,
        equal: (scope) => AppNotifier(
          repository: scope.get<Repository>(name: repositoryName),
        ),
        lazy: lazy,
        dispose: dispose 
          ? (appNotifier) => appNotifier.dispose() 
          : null,
      ),
    ];
  }
}

Future<void> configurableCombineExample() async {
  final rootScope = await Scope.root([
    AppConfigurables(),
  ]);

  final myRepository = rootScope.get<Repository>();
  final myAppNotifier = rootScope.get<AppNotifier>();

  print('myRepository: $myRepository');
  print('myAppNotifier: $myAppNotifier'); 
}

const examples = {
  'scopeRootExample':             scopeRootExample,
  'multipleNamesExample':         multipleNamesExample,
  'privateNameExample':           privateNameExample,
  'omitNameExample':              omitNameExample,
  'scopeRootAsyncExample':        scopeRootAsyncExample,
  'scopePushExample':             scopePushExample,
  'scopeHasExample':              scopeHasExample,
  'scopeGetOrNullExample':        scopeGetOrNullExample,
  'scopeDisposeExample':          scopeDisposeExample,
  'nonLazyFinalExample':          nonLazyFinalExample,
  'configurableInlineExample':    configurableInlineExample,
  'configurableExample':          configurableExample,
  'configurableCombineExample':   configurableCombineExample,
};

void main() async {
  for (final entry in examples.entries) {
    print('--- ${entry.key} ---');
    await entry.value();
  }
}
1
likes
120
pub points
21%
popularity

Publisher

unverified uploader

A declarative dependency injection library which use dart syntax and flutter style

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (LICENSE)

Dependencies

disposal, meta, typedef_equals, typedef_foundation

More

Packages that depend on dart_scope