Using

Once you create a Scope and inject values you can access those values from any method called within the context of the Scope.

By 'context' we mean any method that sits below the Scope's run method on the call stack:

import 'package:scope/scope.dart';
void httpRequestHandler(HttpRequest request)
{
    /// create a scope
    Scope()
        ..value<HttpRequest>(requestKey, request)
        ..value<int>(ageKey, 18)
        ..run(() => a());   /// call a() from within the scope
}

void a() => b();
void b() => c();

void c() {
    // c is within the scope, so we can call 'use' to access values
    // in the scope.
    print('Your are ${use(ageKey)} years old');
    
    print('Your ip is: ${use(requestKey).clientIp}');
}

You can see from the above example that the method c was call from b which was called from a which was called from the Scope's run method thus c is in the Scope's context.

Scope.run -> a ->b ->c

So a, b, and c are all within the Scope's context and have access to the Scope's values.

The use method provide access to injected values that exist in our scope or any ancestors scope.

To obtain an injected value we can call either of the two forms of use. The two forms are equivalent and exist simply for convenience.

     var age = use(ageKey);
     var name = Scope.use(ageKey);

The first version is concise whilst the second version provides better documentation.

ScopeKeys are typed and as such the result of use is also typed.

var ageKey = ScopeKey<int>();
var age = use(ageKey);
age is int;

Missing ScopeKey

If you call use(somekey) and somekey hasn't been added to your Scope then a MissingDependencyException will be thrown.

You can avoid this problem using one of the following techniques:

Default values

The most elegant method is to use a default.

You can set a default when:

  • you create the scopeKey

  • you call use

ScopeKey default

When creating a ScopeKey you can set a default value.

Any time use is called for that ScopeKey and the ScopeKey is not in Scope then the default will be returned.

The default is fixed for the life of the ScopeKey.

ScopeKey<int> countKey = ScopeKey.withDefault<int>(0);

count = use(countKey);

Use default

This is perhaps the most useful method as it allows you to provide a default value from where you call use.

final count = use(countKey, withDefault: () => nextCount++);

We use a lambda () => for the default value to help with performance. The lambda provided to the default argument will only be called if the ScopeKey is missing. This allows the default to call a potentially long running method.

If the ScopeKey was created using Scope.withDefault and you call use with a default value then the default value provided to use will take precedence.

ScopeKey<int> countKey = ScopeKey.withDefault<int>(0);

count = use(countKey, withDefault: () => 1);
expect(count, equals(1));

hasScopeKey

Before calling use can test if the ScopeKey exists by calling hasScopeKey() or Scope.hasScopeKey

final int count;
if (hasScopeKey(countKey)) {
   count = use(countKey);
} else count = 1;

hasScopeValue

hasScopeValue works like hasScopeKey in that it checks if a key is in scope. The difference is that if the key isn't in scope but has a default value then hasScopeValue will return true.

final countKey = ScopeKey.withDefault<int>(10);
final int count;
if (hasScopeValue(countKey)) {
   count = use(countKey);
} else count = 20;

expect(count, equals(10));

isWithinScope

You can check if you are within a Scope by calling isWithinScope() or Scope.isWithinScope().

This method isn't very reliable as it may turn out that you are running in someone else's scope.

final int count;
if (isWithinScope()) {
   count = use(countKey);
} 
else count = 1;

Last updated