scope
Search…
Overriding
The Scope package supports the concept of overriding a scoped key.
The ability to override a scope key solves a number of common design pattern problems with DI particularly when it comes to unit testing.
At any point in your code you can introduce a new Scope that will override existing Scopes and your GlobalScope.
When calling use or Scope.use, the scope package performs a search for the passed key.
The search order is critical to understanding the overriding mechanism.
  • search for the nearest Scope on the call stack
  • search up the hierarchy of Scopes on the call stack
  • search the GlobalScope
  • check if the withDefault argument was passed to use
  • check if the key was created using ScopeKey.withDefault .
  • throw a MissingDependencyException if the key wasn't found.
The best way to understand this is via an example:
import 'package:scope/scope.dart';
final userKey = ScopeKey<User>();
final counterKey = ScopeKey<int>();
final globalUser = User('real');
final testUser = User('test');
final innerUser = User('inner');
final globalDb = Db('global_db');
final liveDb = Db('live_db');
final testDb = Db('test_db');
final dbKey = ScopeKey<Db>.withDefault(liveDb);
/// Demonstrates how the `use` method resolves overridden
/// scope keys.
void main() {
var counter = 0;
GlobalScope()
..value<User>(userKey, globalUser)
..sequence<int>(counterKey, () => counter++);
/// just GlobalScope in scope
assert(use(userKey) == globalUser, 'take from global scope');
/// override the GlobalScope with outerscope
Scope('outerscope')
..value<User>(userKey, testUser)
..run(() {
assert(use(userKey) == testUser, 'take from outerscope');
assert(use(counterKey) == 0, 'always from the GlobalScope');
});
assert(use(userKey) == globalUser, 'take from GlobalScope');
assert(use(counterKey) == 1, 'always from the GlobalScope');
/// override the GlobalScope and outerscope with
/// innerscope
Scope('outerscope')
..value<User>(userKey, testUser)
..run(() {
assert(use(counterKey) == 2, 'always from the GlobalScope');
Scope('innerscope')
..value<User>(userKey, innerUser)
..run(() {
assert(use(userKey) == innerUser, 'take from innerscope');
assert(use(counterKey) == 3, 'always from the GlobalScope');
});
assert(use(userKey) == testUser, 'take from outerscope');
});
/// we are out of all Scope's so GlobalScope back in play
assert(use(userKey) == globalUser, 'take from GlobalScope');
assert(use(counterKey) == 4, 'always from the GlobalScope');
/// No key in scope; get the keys default
assert(use(dbKey) == liveDb, 'take from the withDefault value of the dbKey');
/// No key in scope; use the default value provided to use
assert(use(dbKey, withDefault: () => testDb) == testDb,
'take from the withDefault provided to use');
/// inject a dbKey into the global scope
GlobalScope().value<Db>(dbKey, globalDb);
/// Global key in scope; so use it.
assert(
use(dbKey, withDefault: () => testDb) == globalDb, 'use the global key');
/// override the GlobalScope with outerscope
Scope('outerscope')
..value<Db>(dbKey, globalDb)
..run(() {
assert(use(dbKey) == globalDb, 'take from outerscope');
assert(use(counterKey) == 5, 'always from the GlobalScope');
});
}
class User {
User(this.name);
String name;
}
class Db {
Db(this.databaseName);
String databaseName;
}
Copy link