Every bind<T>() must be completed with one of the targets above. bind<T>()
on its own registers nothing that can be resolved, so installing such a module
throws a StateError naming the type, the name (for a named binding) and the
module:
classNetworkModuleextendsModule {
@override
voidbuilder(Scope scope) {
bind<String>().withName('baseUrl'); // no target — never resolvable
}
}
scope.installModules([NetworkModule()]);
// StateError: Binding for `String` named 'baseUrl' in module NetworkModule has
// no target, so it can never be resolved. Complete it with toInstance(),
// toProvide(), toProvideWithParams(), toProvideAsync() or
// toProvideAsyncWithParams().
⚠️ Important note about using toInstance in Module builder:
If you register a chain of dependencies via toInstance inside a Module’s builder, do not call scope.resolve<T>() for types that are also being registered in the same builder — at the moment they are registered.
CherryPick initializes all bindings in the builder sequentially. Dependencies registered earlier are not yet available to resolve within the same builder execution. Trying to resolve just-registered types will result in an error (Can't resolve dependency ...).
How to do it right:
Manually construct the full dependency chain before calling toInstance:
voidbuilder(Scope scope) {
final a =A();
final b =B(a);
final c =C(b);
bind<A>().toInstance(a);
bind<B>().toInstance(b);
bind<C>().toInstance(c);
}
Wrong:
voidbuilder(Scope scope) {
bind<A>().toInstance(A());
// Error! At this point, A is not registered yet.
bind<B>().toInstance(B(scope.resolve<A>()));
}
Wrong:
voidbuilder(Scope scope) {
bind<A>().toProvide(() =>A());
// Error! At this point, A is not registered yet.
bind<B>().toInstance(B(scope.resolve<A>()));
}
Note: This limitation applies only to toInstance. With toProvide/toProvideAsync and similar providers, you can safely use scope.resolve<T>() inside the builder.
⚠️ Special note regarding .singleton() with toProvideWithParams() / toProvideAsyncWithParams():
If you declare a binding using .toProvideWithParams(...) (or its async variant) and then chain .singleton(), only the very firstresolve<T>(params: ...) will use its parameters; every subsequent call (regardless of params) will return the same (cached) instance.
final a = scope.resolve<Service>(params:1); // creates Service(1)
final b = scope.resolve<Service>(params:2); // returns Service(1)
print(identical(a, b)); // true
Use this pattern only when you want a “master” singleton. If you expect a new instance per params, do not use .singleton() on parameterized providers.
ℹ️ Note about .singleton() and .toInstance():
Calling .singleton() after .toInstance() does not change the binding’s behavior: the object passed with toInstance() is already a single, constant instance that will be always returned for every resolve.
It is not necessary to use .singleton() with an existing object—this call has no effect.
.singleton() is only meaningful with providers (such as toProvide/toProvideAsync), to ensure only one instance is created by the factory.