# Overview

## Scope

Scope provides Inversion of Control using the dependency injection (DI) pattern for Dart applications.

Scope allows you to inject values into a scope and then 'use' those dependencies from any method (or constructor) called within that scope.

Scope is not a replacement for the likes of Provider. Provider does dependency injection for your BuildContext whilst **Scope provides DI for your call stack.**

For Java developers, Scope provides similar functionality to a thread-local variables

Authors:&#x20;

* Philipp Schiffmann <philippschiffmann93@gmail.com>
* S. Brett Sutton

Scope is a reimagining of Philipp's zone\_id package. All credit goes to Phillipp's original implementation without which Scope wouldn't exist.

## Sponsored by OnePub

Help support Scope by supporting [OnePub](https://onepub.dev/drive/7e385313-5b0e-4874-b635-6098653a88f8), the private Dart repository.&#x20;

OnePub allows you to privately share Dart packages between your own projects or with colleagues.

Try it for free and publish your first private package in seconds.

| ![](/files/RfavILIzFVmXnTxAm8Lt) | <p>Publish a private package in five commands:</p><p><mark style="color:green;"><code>dart pub global activate onepub</code></mark></p><p><mark style="color:green;"><code>onepub login</code></mark></p><p><mark style="color:green;"><code>cd \<my package></code></mark></p><p><mark style="color:green;"><code>onepub pub private</code></mark> </p><p><mark style="color:green;"><code>dart pub publish</code></mark></p> |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |

Scope is available on pub.dev at:

{% embed url="<https://pub.dev/packages/scope>" %}

This is most easily understood via an example:

```dart
import 'package:scope/scope.dart';

/// create a key to access a scoped value
final ageKey = ScopeKey<int>();

void main() {
    /// create a Scope
    Scope()
    
    /// inject a value
    ..value<int>(ageKey, 18)
    
    /// run some code within the Scope
    ..run(() => a();
}
void a() => b();

/// `use` the injected value by its key 'ageKey'
void b() => print('You are ${use(ageKey)} years old');
```

We create a Scope within main and call the method `a()`  which calls `b()`. Both a() and b() are within the declared scope and therefore have access to the injected value `ageKey`.&#x20;

To access an injected value you call the `use` method.

## Inject multiple values of the same type

Scope allows you to inject multiple values of the same type

```dart
final ageKey = ScopeKey<int>();
final carKey = ScopeKey<Car>();
final otherCarKey = ScopeKey<Car>();

Scope()
..value<int>(ageKey, 18)
..value<Car>(carKey, new Car('red'))
..value<Car>(otherCarKey, new Car('blue'))
..run(() {
  print('age:  ${use(ageKey)} colour: ${use(carKey)} other: ${use(otherCarKey)}');
  // age: 18: colour: red other: blue
});
```

## Inject Single values and Sequences

You can inject values generated from a factory method as a `single` value or a `sequence` of values.

The difference between injecting a `single` value and a `value` is that the `single`'s factory method has access to all other values injected into the scope.

A `sequence` generates a new value each time the `use` method is called with its key.

```dart
import 'package:scope/scope.dart';
import 'package:money2/money2.dart';
final ageKey = ScopeKey<int>();
final seedKey = ScopeKey<String>();
final lotteryKey = ScopeKey<Money>();
Scope()
..value<int>(ageKey, 18)

/// call randomValue just once using ageKey as the seed.
..single<String>(seedKey, () => randomValue(use(ageKey))

/// call randomWinnings each time `use(lotteryKey)` is called.
..sequence<String>(lotteryKey, () => randomWinnings(use(seedKey))

..run(() {
  print('age:  ${use(ageKey)} you won: ${use(lotteryKey)}');
  // age: 18: you won: $2000.00
  
  Money randomWinnings(String seedValue) => ...

```

## Nesting

Scope also allows you to nest scopes to any level.

```dart
final ageKey = ScopeKey<int>();
final carKey = ScopeKey<Car>();
Scope()
..value<int>(ageKey, 18)
..value<Car>(carKey, new Car('red'))
..run(() {
    // use values the other scope
    print('age:  ${use(ageKey)} colour: ${use(carKey)}');
  // age: 18: colour: red

  // create a nested scope 
  Scope()
  ..value<Car>(carKey, new Car('green'))
  ..run(() {
    /// use values from the inner and outer scope.
    print('age:  ${use(ageKey)} colour: ${use(carKey)}');
   // age: 18: colour: green
  });
});

```


# Installing

To add Scope to your project...

#### Depend on it

Run this command from your package root.

With Dart:

```shell
 $ dart pub add scope
```

This will add a line like this to your package's pubspec.yaml (and run `dart pub get`):

```yaml
dependencies:
  scope: ^1.0.0
```

#### Import it

Now in your Dart code add:

```dart
import 'package:scope/scope.dart';
```


# Scope

Scope provides Inversion of Control using the dependency injection (DI) pattern for Dart applications.

Scope allows you to inject values into a scope and then 'use' those dependencies from any method (or constructor) called within that scope.

Scope provides DI for your call stack.

This is most easily understood via an example:

```dart
import 'package:scope/scope.dart';

/// create a key to access a scoped value
final ageKey = ScopeKey<int>();

void main() {
    /// create a Scope
    Scope()
    
    /// inject a value
    ..value<int>(ageKey, 18)
    
    /// run some code within the Scope
    ..run(() => a();
}
void a() => b();

/// `use` the injected value by its key 'ageKey'
void b() => print('You are ${use(ageKey)} years old');
```

We create a Scope within main and call the method `a()`  which calls `b()`. Both a() and b() are within the declared scope and therefore have access to the injected value `ageKey`.&#x20;

To access an injected value you call the `use` method.

##


# Creating

The Scope API uses a builder pattern allowing you to inject any number of strictly typed values.

{% hint style="info" %}
Use the [lints](https://pub.dev/packages?q=lints) package to improve your code quality or [lint\_hard](https://pub.dev/packages/lint_hard) package if you want to grow hair on your chest.
{% endhint %}

You can then 'use' the injected values from any method called from within the context of the Scope.

```dart
import 'package:scope/scope.dart';

import 'my_scope_keys.dart';

void main() 
{
    Scope('main')
    ..value<int>(ageKey, 18)
    ..value<String>(nameKey, 'brett')
    ..run(() {
        someMethod();
    });
}
    
void someMethod() {
    // get values stored in the scope
    var age = use(ageKey);
    var name = use(nameKey);
    print('name: $name, age: $age');
}

```

In the above example we inject two values:

* an int with a key 'ageKey' and a value 18.
* a String with a key 'nameKey' and a value of 'brett'.

The age and name values are retrieved using the `use` method and the ScopeKey they where injected with.

You will notice that we pass the string 'main' to the Scope. This is an optional argument `debugName`. We recommend that you always pass a `debugName` as it is included when an Scope related exception is thrown and makes it easier to identify the source of a problem.

### ScopeKey

To inject and use a value you must create a typed key for each value using a ScopeKey:

```dart
// my_scope_keys.dart
final ageKey = ScopeKey<int>('ageKey');
final nameKey = ScopeKey<String>('nameKey');
final monthKey = ScopeKey<String>();
```

As the ScopeKey is typed the values returned from the `use` call are also correctly typed.

ScopeKeys are declared globally and it's is standard practice to place you Keys in a separate dart library as they need to be available at both the injection site and the `use` site.

You will notice that with the first two examples we provide the optional string argument `debugName`.  We recommend that you always pass a `debugName` as it is included when an ScopeKey related exception is thrown and makes it easier to identify the source of a problem.

## Example

We start by defining ScopeKeys for each value we want to inject.

```dart
final greetingKey = ScopeKey<String>();
final emphasisKey = ScopeKey<String>()
final dbKey = ScopeKey<Db>();
```

When then create a client (the Greeter class) that will 'use' values injected into the Scope.

```dart
class Greeter {
  Greeter()
      : greeting = use(greetingKey),
        emphasis = use(emphasisKey);

  final String greeting;
  final int emphasis;

  void greet(String name) {
    print('$greeting, $name${"!" * emphasis}');
    use(dbKey).createUser(name);
  }
}
```

Values are made available by creating a`Scope and injecting each of key/value pair into the Scope`.&#x20;

Once we have injected values into the Scope we call the Scope's `run` method.

Any method called directly or indirectly from within the `run` method has access to each of the values injected into the Scope.

```dart
import 'package:scope/scope.dart';
void main() {
  Scope()
    ..value<String>(greetingKey, 'Hello')
    ..value<int>(emphasisKey, 3)
    ..run(() {
    // The greet method calls `use` to retrieve the registered values
    Greeter().greet('world'); // 'Hello, world!!!'
  });

  Greeter().greet('you'); // throws: `MissingDependencyException`, because 
                          // `use()` is called outside the `Scope.run` method.
                          // The previously registered values arn't available
                          // outside of the scope of the run method.
}
```

You can create a Scope anywhere in your code that it might be useful.&#x20;


# 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:

```dart
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.

```dart
     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.

```dart
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.

```dart
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`.

```dart
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.

```dart
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`

```dart
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.

```dart
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.

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


# Detecting

It can often be useful to determine if you are running in a Scope.

You can detect if your code is running within a Scope by calling:

```dart
Scope.isWithinScope();

// or

isWithinScope();
```

You can also detect if a specific ScopeKey exists by calling:

```dart
if (hasScopeKey(ageKey))
{
   // change my behaviour
}

/// or

if (Scope.hasScopeKey(ageKey))
{
   // change my behaviour
}


```

Both forms of isWithinScope and hasScopeKey are identical and are provided for consistency with the two forms of the `use` method.

Detecting if you are within a Scope is really useful for unit testing.

Your unit tests can inject a ScopeKey and change your code can then change its behaviour when running within a unit test.

This can be easier then setting up a full mock framework.


# Nesting

Scopes can be nested with the same keys or different keys injected at each level.

When you inject a key/value pair into a nested Scoped and then call `use,` the value from the closest Scope with a matching key is used.

If the key isn't in the immediate Scope we search up through parent scopes.

```dart
import 'package:scope/scope.dart';
void main() {

  // parent scope
  Scope()
    ..value<String>(greetingKey, 'Hello')
    ..value<int>(emphasisKey,  1)
     ..run(() {
    
          /// Nest child Scope (1)
          Scope()
          ..value<String>(greetingKey, 'Good day')
          ..run(() {
               Greeter().greet('Philipp'); // 'Good day, Philipp!'
          });

         /// Sibling of (1) = also nested witin the parent scope
         Scope()
          ..value<int>(emphasisKey, 3)
          ..run(() {
               Greeter().greet('Paul'); // 'Hello, Paul!!!'
         });
  });
}
```

There is no limit to the level of nesting that can be used.


# Async

Scopes also work across asynchronous calls in that a method called asynchronously can call the \`use' method to access values from the Scope.

```dart
final keyAge = ScopeKey<int>('age');

final one = await (Scope()
    ..value<int>(keyAge, 18))
    .run<Future<int>>(() async {
        /// setup up a 1 second delay and call `use` within
        /// a future.
        final delayedvalue =
            Future<int>.delayed(const Duration(seconds: 1), () => use(keyAge));
        return delayedvalue;
    });

expect(one, equals(18));
```

The Future MUST also be created within the Scope.

{% hint style="info" %}
Just because a method runs concurrently to a Scope does not mean the method has access to the Scope.
{% endhint %}

Just because another async method runs during the scope's existence does not mean it has access to the Scope. It must be called from a method within the Scope's call stack.

e.g.

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

Where -> means calls; a, b and c have access to the the Scope.

main -> d -> f -> c

Even if these methods are run asynchronously whilst the Scope exists they will not have access to the Scope as they were not called from the Scope's `run` method.

## Calling async methods

You will often need the Scope's `run` method to make async calls and have the call wait until the run completes.

This is simple to do:

```dart
  /// May sync call
  Scope()
    ..value<bool>(tenatId, 10)
    ..value<bool>(bypassTenantKey, true)
    ..run(()  {
      action();
    });
    
  /// Make async call.
  await (Scope()
    ..value<bool>(tenatId, 10)
    ..value<bool>(bypassTenantKey, true)
     )
     .run(() async {
    await action();
  });
```

There are **five** key changes required to make the async call:

* add an await before the call to Scope()
* add async after the \`run(()\`&#x20;
* wrap the Scope and value builder calls in parenthesis
* change `..run` to `.run`
* ``add `await` before the call to action``

Its critical that you note step 3.&#x20;

The `..run` notation tells dart to call `run` but still return `Scope`.  It is the `run` method that is async so we need to ensure that the result of the expression is the result of run not a Scope.


# Single and Sequence factories

Scope allows you to define values based on factory methods (callbacks) as a single value or a sequence of values.

### Single

A single value is simply a value that is provided by making a call to a function rather than providing a fixed value.

```dart
import 'package:scope/scope.dart';

Scope()
..value<String>(nameKey, 'brett')
..value<DateTime>(dobKey, DateTime(year: 2000, month: 1, day 1))
..single<String>(ageKey, () => DateTime.now().difference(use(dobKey)).inYears) // called only once.
..run(() {
     print('age: ${use(ageKey)}');
 });
```

The call to `single` in the above example takes two arguments:

* ScopeKey -  `ageKey`
* Factory method - `() => DateTime.now().difference(use(dobKey)).inYears`

#### Evaluation

The `single` value is calculated once by calling the `factory` method when the `run` method is called. The calculated factory value is then **fixed** for the life of the Scope.

You can think of this as eager evaluation of the factory method.

{% hint style="info" %}
**`Factory`** methods have access to other 'in Scope' variables where as the **value** call does not.
{% endhint %}

The factory method can `use` other `values` declared in the same scope (or a parent scope). This is the main difference between the `single` call on line 4 and the `value` call on line 3. Both call functions but the `factory` method has access to other 'in Scope' variables where as the value call does not.

Be careful to avoid circular dependencies!

### sequence

A `sequence` value is injected in a similar way to the `single` value. The difference is that the `sequence` method is NOT called when the run method is called but each time the `use` method is called with the `sequence's` ScopeKey.

```dart
import 'package:scope/scope.dart';
Scope()
..value<String>(nameKey, 'brett')
..value<DateTime>(dobKey, DateTime(year: 2000, month: 1, day 1)
..sequence<String>(ageKey, () => DateTime.now().difference(use(dobKey)).inYears) // called each time `use(ageKey)` is called within this Scope.
..run(() {
     print('age: ${use(ageKey)}');
 });
```

You can think of this as lazy evaluation of the sequence value.

A `sequence` can be used to recalculate a value each time it is used and could be used to provide a sequence of values (such as a counter or a random number generator).


# Returning values

You can return a  value from a Scope's run method.

```dart
import 'package:scope/scope.dart';

void main() {

  /// buld the scope
  final greeter = (Scope()
     ..value<String>(greetingToken, 'Hello')
     ..value<int>(emphasisToken,  1))
     /// run code in the scope getting the return value
     .run<Greeter>(Greeter());

  /// use the returned value  
  greeter.greet('world'); // `greeter` can now be used outside of the scope
                          // but will have no access to scope keys.
}
```

***


# Mutable values

Scope doesn't impose any rules around mutation of values store in the Scope.&#x20;

If you add an immutable object such as a `String` to the Scope then you won't be able to change it.

Values cannot be added or removed from the Scope after the Scope.run method is called.

However if you add an object such as a StringBuffer as a value then you can mutate the contents of the StringBuffer as you would normally do.

You can add any type of object into a Scope including objects based on your own classes.


# GlobalScope

{% hint style="info" %}
GlobalScope is only available in the 2.3 beta, is experimental and likely to change.
{% endhint %}

A GlobalScope implements the Singleton pattern with improvements.

When you inject a value into a GlobalScope it is available to your entire application (including in flutter build methods).

A classic example of using a GlobalScope is to hold the currently logged in user.

```dart
final userKey = ScopeKey<User>();
final user = validateLogin(username, password);
if (user != null) {
    GlobalScope().value(userKey, user);
}
```

{% hint style="success" %}
We use `guse` rather than `use` to get global ScopeKeys.
{% endhint %}

Now that you have the user you can get them from anywhere in your code.

```dart
final user = use(userKey);
```

### An improved singleton

The problem with the Singleton pattern is that it doesn't play nicely when it comes to unit testing.

GlobalScope solves this problem by being aware of any Scope keys and allowing a Scope key to override a GlobalScope key.

```dart
void main() {
    GlobalScope().value(userKey, user);
}

test('a unit test', () {
    final user = use(userKey); // return user injected in main
    Scope().
    ..value(userKey, testUser) // override the userKey
    ..run(() => 
    {
        final user = use(userKey); // returns testUser
    });
    // we are back outside the Scope.
    final user = use(userKey); // return user injected in main
}
```


# 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:

```dart
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;
}

```


# Type safety

Using strict type safety when using Scope is highly recommended as it will move many runtime errors into compile time errors.

When injecting a value be sure to declare its type.

```dart
import 'package:scope/scope.dart';

const ageKey = ScopeKey<int>();
const countKey = ScopeKey<int>();

/// Missing type when value is called.
/// When you try to `use` this key as an int
/// you will be in for a nasty surprise.
Scope()
  ..value(ageKey, 'my name');
  
/// type is correctly passed
Scope()
  ..value<int>(countKey, 10);
```

To ensure type safety when providing values we recommend that you use the lints package and add the following to your analysis\_options.yaml file:

```yaml
analyzer:
  language:
    strict-raw-types: true
    strict-inference: true
    # only available from dart 2.16
    strict-casts: true
  strong-mode:
    implicit-casts: false
    implicit-dynamic: false
```


# Debugging

The Scope package is generally very easy to use as it works as expected in almost all cases.

### debugName

For the times when you are having problems, you can pass a `debugName` to each `Scope` and `ScopeKey`.

{% hint style="success" %}
If you are having problems debugging a Scope issue, start by adding a unique debugName to every Scope and ScopeKey.
{% endhint %}

We recommend that you pass the `debugName` to all your Scope's and ScopeKeys. It will make debugging problems much easier if you get into the habit.

```dart
final key = ScopeKey<int>('debug key name');

Scope('debug scope name')
..value(key, 1);
```

When running in the debugger the `debugName` will be displayed.

You can also print out the `debugName` for a `ScopeKey` or `Scope` by calling their `toString()` method.

### Nested Scopes

Probably the most complex situation is when you have nested Scopes.  Check the call stack for multiple Scope instances.

When you call `use` within a Nested Scope it will search 'up' the call stack looking for the first Scope. If the key is found in that Scope then that value is returned. If the key isn't found we keep searching up the stack for additional Scopes.

The methods `withinScope` and `hasScopeKey` can also help debugging problems when calling `use`.


# How Scope Works

### How it works

Scope uses Dart's native [Zone](https://api.dart.dev/stable/2.15.1/dart-async/Zone-class.html) implementation.

Zones allow you to associate a `map` of key/value pairs with each zone.

Scope takes advantage of that fact and wraps lots of syntactic sugar around the Zone to make it easy to use.


# Best Practice

Here are a few tips for using Scope in no particular order.

***

## Not a substitute for Provider et al

Scope is not a substitute for the likes of Provider which works to provide values for your Flutter BuildContext. The Flutter build method isn't called on your stack (as it's called by the Flutter framework) and Scope only works for methods calls nested within the Scope's run method.

## Place ScopeKeys in their own library

ScopeKeys need to be visible at the point where you inject a value and where you `use` the value.

Place your ScopeKeys in a separate Dart library to make them easy to import where they are needed.

```dart
import 'package:scope/scope.dart';
final ageKey = ScopeKey<int>();
final nameKey = ScopeKey<String>();
final monthKey = ScopeKey<String>();
final dbKey = ScopeKey<Db>();
```

## Use scope when you have to pass values down

Scope is intended for use cases where you create some resource in a top level method and then need access to that resource way down in your call hierarchy and you don't want to have to pass the value down as an argument to each method.

## Consider Scope for server side apps

Scope works great for Server Side and Cli apps.

Use a Scope to hold a Session object when servicing a http request or a database connection.

## Use Scope in Flutter apps

Scope is not a replacement for the likes of Provider and Bloc. Provider and Bloc provide DI for you widget build methods. &#x20;

Scope still has a part to play in Flutter apps. If you are using a database or making network calls from your Flutter app then Scope can be a useful tool in your kit.

## Used debugName

Both Scope and ScopeKey allow you to passing in a argument `debugName`. The `debugName` is included in Exceptions which can make it much easier to find the particular Scope or ScopeKey that is the source of the problem.

```dart
static final ScopeKey<int> tenantIdKey = ScopeKey<int>('tenantIdKey');
Scope('withTenant')
..value<int>(Tenant.tenantIdKey, tenantId)
..run(() {
  action();
});  
```

For ScopeKey's use the name of the key.  If you are a package developer prefix it with the name of your package e.g. `scope.tenantIdKey`

For Scope's use the name of the method or class that create the scope.

The key thing is that when you get an exception there is enough information to identify the Scope or ScopeKey.

## Replace Singletons

A Scope is often a good replacement for a Singleton.

## Use Scope in unit tests

Scope works well when building unit tests.  The Dart test package is able to run tests concurrently. A Scope lets each unit tests hold its own set of values.  Deeply nested methods can check if a ScopeKey exists via `Scope.hasScopeKey` and modify their behaviour. This can be easier than setting up an entire mock framework.

## Use Scope just about everywhere

You can call `use()` inside class constructors, in individual methods or even top-level functions outside of any class.

The only criteria is that when you call 'use' a Scope can be found in some parent method/function somewhere on the call stack.

## Document your injected values

Other people can't see what values you inject into a Scope. Make sure to explicitly list all dependencies of your public API in its doc comments!&#x20;

## Create Wrapper classes

To make your Scope easier to use create specialised wrapper functions or classes that use a nomenclature specific to the usage domain.&#x20;

In this example we wrap a Scope in a Transaction class.

A user creates a Transaction class that obtains a Db connection which the user can `use` anywhere within the scope of the Transaction by calling Transaction.db.

```dart
import 'package:scope/scope.dart' as scope;

final dbKey = ScopeKey<Db>();

// provide a transacition class that acquires a db cnnection
class Transaction
{
     void run(void Function() action)
     {
          var db = DbPool.acquire();
          Scope()
             ..value(dbKey, db)
             ..run(action);
           DbPool.release(db);
     }
    
     /// To access the db inscope for this transaction
     static DB db() => use(dbKey);
}

/// Use the transaction class
void main() 
{
     Transaction().run() {
          createUser();
        });
 }
 
 void createUser() {
       /// get the db from transaction
       var db = Transation.db();
       db.insertUser();
}
```

This way, a consumer of your package doesn't have to look up what `ScopeKey`s are, and gets a type-safe list of all of your public dependencies at a glance.


# How is Scope different from 'x'

We receive a lot of questions about how Scope is different from other packages in the Dart ecosystem. So here are a few comparisons.

If one of these is your package and I've mis-represented it, apologies in advance and drop me a line with any corrections.

## Get It

GetIt is quite different in that it is essentially a singleton.

There is only one instance of getit for you whole application that you can add services into.

When using Scope you will end up with lots of instances of Scope and each one may have different values injected.&#x20;

As Dart is asynchronous you may have multiple Scopes active at one time (even before you start nesting scopes).

A classic example is a call to a database (db) connection.

From a FutureBuilder you might do a db call.

So you get a db from your pool and push it into a scope.

Now any code called within that scope has access to that db connection.

**Now this is where Scope is different to getIt**

If you have another FutureBuilder running at the same time it may also need to access the db.

So you create another scope with its own db connection.

The two future builders now run happily, each with their on db connection and they won't interfere with each other.

Usage of Scope is complementary to GetIt and many apps will use both.

## Provider

Provider and similar packages (river pod) also provide dependency injection but they do it for you widget build method.

In Flutter widget build methods are not on your call stack. Instead the build method is called from the Flutter framework.

This is one reason why Flutter has been so problematic as its hard to pass data around. You can't just pass things down your call stack.

Scope on the other hand does dependency injection for you call stack. It works for any methods/functions/constructors that are called on your call stack.

Usage of Scope is complementary to GetIt and many apps will use both.


