Best Practice
Here are a few tips for using Scope in no particular order.
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.
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.
import 'package:scope/scope.dart';
final ageKey = ScopeKey<int>();
final nameKey = ScopeKey<String>();
final monthKey = ScopeKey<String>();
final dbKey = ScopeKey<Db>();
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.
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.
Scope is not a replacement for the likes of Provider and Bloc. Provider and Bloc provide DI for you widget build methods.
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.
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.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.
A Scope is often a good replacement for a Singleton.
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.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.
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!
To make your Scope easier to use create specialised wrapper functions or classes that use a nomenclature specific to the usage domain.
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.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.Last modified 1yr ago