mirror of
https://github.com/pese-git/cherrypick.git
synced 2026-05-16 10:10:43 +00:00
feat(cherrypick): split resolver into type-specialized classes with eager/lazy singleton and FutureOr support
This commit is contained in:
@@ -11,6 +11,8 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:cherrypick/src/binding_resolver.dart';
|
import 'package:cherrypick/src/binding_resolver.dart';
|
||||||
|
|
||||||
/// {@template binding_docs}
|
/// {@template binding_docs}
|
||||||
@@ -69,6 +71,8 @@ class Binding<T> {
|
|||||||
|
|
||||||
CherryPickObserver? observer;
|
CherryPickObserver? observer;
|
||||||
|
|
||||||
|
bool get _isSilentObserver => observer is SilentCherryPickObserver;
|
||||||
|
|
||||||
// Deferred logging flags
|
// Deferred logging flags
|
||||||
bool _createdLogged = false;
|
bool _createdLogged = false;
|
||||||
bool _namedLogged = false;
|
bool _namedLogged = false;
|
||||||
@@ -191,10 +195,9 @@ class Binding<T> {
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
/// This restriction only applies to [toInstance] bindings.
|
/// This restriction only applies to [toInstance] bindings.
|
||||||
// ignore: deprecated_member_use_from_same_package
|
|
||||||
/// With [toProvide]/[toProvideAsync] you may freely use `scope.resolve<T>()` in the builder or provider function.
|
/// With [toProvide]/[toProvideAsync] you may freely use `scope.resolve<T>()` in the builder or provider function.
|
||||||
Binding<T> toInstance(Instance<T> value) {
|
Binding<T> toInstance(FutureOr<T> value) {
|
||||||
_resolver = InstanceResolver<T>(value);
|
_resolver = InstanceResolver.create<T>(value);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,8 +208,8 @@ class Binding<T> {
|
|||||||
/// bind<Api>().toProvide(() => ApiService());
|
/// bind<Api>().toProvide(() => ApiService());
|
||||||
/// bind<Db>().toProvide(() async => await openDb());
|
/// bind<Db>().toProvide(() async => await openDb());
|
||||||
/// ```
|
/// ```
|
||||||
Binding<T> toProvide(Provider<T> value) {
|
Binding<T> toProvide(FutureOr<T> Function() value) {
|
||||||
_resolver = ProviderResolver<T>((_) => value.call(), withParams: false);
|
_resolver = ProviderResolver.create<T>(value);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,24 +219,32 @@ class Binding<T> {
|
|||||||
/// ```dart
|
/// ```dart
|
||||||
/// bind<User>().toProvideWithParams((params) => User(name: params["name"]));
|
/// bind<User>().toProvideWithParams((params) => User(name: params["name"]));
|
||||||
/// ```
|
/// ```
|
||||||
Binding<T> toProvideWithParams(ProviderWithParams<T> value) {
|
Binding<T> toProvideWithParams(FutureOr<T> Function(dynamic) value) {
|
||||||
_resolver = ProviderResolver<T>(value, withParams: true);
|
_resolver = ProviderResolver.createWithParams<T>(value);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated('Use toInstance instead of toInstanceAsync')
|
@Deprecated('Use toInstance instead of toInstanceAsync')
|
||||||
Binding<T> toInstanceAsync(Instance<T> value) {
|
Binding<T> toInstanceAsync(FutureOr<T> value) {
|
||||||
return toInstance(value);
|
return toInstance(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated('Use toProvide instead of toProvideAsync')
|
/// Asynchronous variant of [toProvide] for providers that return [Future<T>].
|
||||||
Binding<T> toProvideAsync(Provider<T> value) {
|
///
|
||||||
return toProvide(value);
|
/// Prefer this over [toProvide] when the provider is async, so the resolver
|
||||||
|
/// is type-safe and avoids runtime detection overhead.
|
||||||
|
Binding<T> toProvideAsync(AsyncProvider<T> value) {
|
||||||
|
_resolver = ProviderResolver.async<T>(value);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated('Use toProvideWithParams instead of toProvideAsyncWithParams')
|
/// Asynchronous variant of [toProvideWithParams] for providers that return [Future<T>].
|
||||||
Binding<T> toProvideAsyncWithParams(ProviderWithParams<T> value) {
|
///
|
||||||
return toProvideWithParams(value);
|
/// Prefer this over [toProvideWithParams] when the provider is async, so the resolver
|
||||||
|
/// is type-safe and avoids runtime detection overhead.
|
||||||
|
Binding<T> toProvideAsyncWithParams(AsyncProviderWithParams<T> value) {
|
||||||
|
_resolver = ProviderResolver.asyncWithParams<T>(value);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Marks this binding as singleton (will only create and cache one instance per scope).
|
/// Marks this binding as singleton (will only create and cache one instance per scope).
|
||||||
@@ -281,6 +292,10 @@ class Binding<T> {
|
|||||||
/// ```
|
/// ```
|
||||||
T? resolveSync([dynamic params]) {
|
T? resolveSync([dynamic params]) {
|
||||||
final res = resolver?.resolveSync(params);
|
final res = resolver?.resolveSync(params);
|
||||||
|
if (_isSilentObserver) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
if (res != null) {
|
if (res != null) {
|
||||||
observer?.onDiagnostic(
|
observer?.onDiagnostic(
|
||||||
'Binding resolved instance: ${T.toString()}',
|
'Binding resolved instance: ${T.toString()}',
|
||||||
@@ -313,6 +328,10 @@ class Binding<T> {
|
|||||||
/// ```
|
/// ```
|
||||||
Future<T>? resolveAsync([dynamic params]) {
|
Future<T>? resolveAsync([dynamic params]) {
|
||||||
final future = resolver?.resolveAsync(params);
|
final future = resolver?.resolveAsync(params);
|
||||||
|
if (_isSilentObserver) {
|
||||||
|
return future;
|
||||||
|
}
|
||||||
|
|
||||||
if (future != null) {
|
if (future != null) {
|
||||||
future
|
future
|
||||||
.then((res) => observer?.onDiagnostic(
|
.then((res) => observer?.onDiagnostic(
|
||||||
|
|||||||
@@ -13,86 +13,37 @@
|
|||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
/// Represents a direct instance or an async instance ([T] or [Future<T>]).
|
/// Synchronous factory: `T Function()`.
|
||||||
/// Used for both direct and async bindings.
|
typedef Provider<T> = T Function();
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
/// ```dart
|
|
||||||
/// Instance<String> sync = "hello";
|
|
||||||
/// Instance<MyApi> async = Future.value(MyApi());
|
|
||||||
/// ```
|
|
||||||
typedef Instance<T> = FutureOr<T>;
|
|
||||||
|
|
||||||
/// Provider function type for synchronous or asynchronous, parameterless creation of [T].
|
/// Parameterized synchronous factory: `T Function(dynamic)`.
|
||||||
/// Can return [T] or [Future<T>].
|
typedef ProviderWithParams<T> = T Function(dynamic);
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
/// ```dart
|
|
||||||
/// Provider<MyService> provider = () => MyService();
|
|
||||||
/// Provider<Api> asyncProvider = () async => await Api.connect();
|
|
||||||
/// ```
|
|
||||||
typedef Provider<T> = FutureOr<T> Function();
|
|
||||||
|
|
||||||
/// Provider function type that accepts a dynamic parameter, for factory/parametrized injection.
|
/// Asynchronous factory: `Future<T> Function()`.
|
||||||
/// Returns [T] or [Future<T>].
|
typedef AsyncProvider<T> = Future<T> Function();
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
/// ```dart
|
|
||||||
/// ProviderWithParams<User> provider = (params) => User(params["name"]);
|
|
||||||
/// ```
|
|
||||||
typedef ProviderWithParams<T> = FutureOr<T> Function(dynamic);
|
|
||||||
|
|
||||||
/// Abstract interface for dependency resolvers used by [Binding].
|
/// Parameterized asynchronous factory: `Future<T> Function(dynamic)`.
|
||||||
/// Defines how to resolve instances of type [T].
|
typedef AsyncProviderWithParams<T> = Future<T> Function(dynamic);
|
||||||
///
|
|
||||||
/// You usually don't use this directly; it's used internally for advanced/low-level DI.
|
/// Internal interface for resolvers managed by [Binding].
|
||||||
abstract class BindingResolver<T> {
|
abstract class BindingResolver<T> {
|
||||||
/// Synchronously resolves the dependency, optionally taking parameters (for factory cases).
|
|
||||||
/// Throws if implementation does not support sync resolution.
|
|
||||||
T? resolveSync([dynamic params]);
|
T? resolveSync([dynamic params]);
|
||||||
|
|
||||||
/// Asynchronously resolves the dependency, optionally taking parameters (for factory cases).
|
|
||||||
/// If instance is already a [Future], returns it directly.
|
|
||||||
Future<T>? resolveAsync([dynamic params]);
|
Future<T>? resolveAsync([dynamic params]);
|
||||||
|
|
||||||
/// Marks this resolver as singleton: instance(s) will be cached and reused inside the scope.
|
|
||||||
void toSingleton();
|
void toSingleton();
|
||||||
|
|
||||||
/// Returns true if this resolver is marked as singleton.
|
|
||||||
bool get isSingleton;
|
bool get isSingleton;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Concrete resolver for direct instance ([T] or [Future<T>]). No provider is called.
|
/// Resolver for a pre-built synchronous instance.
|
||||||
///
|
class SyncInstanceResolver<T> implements BindingResolver<T> {
|
||||||
/// Used for [Binding.toInstance].
|
final T _instance;
|
||||||
/// Supports both sync and async resolution; sync will throw if underlying instance is [Future].
|
|
||||||
/// Examples:
|
|
||||||
/// ```dart
|
|
||||||
/// var resolver = InstanceResolver("hello");
|
|
||||||
/// resolver.resolveSync(); // == "hello"
|
|
||||||
/// var asyncResolver = InstanceResolver(Future.value(7));
|
|
||||||
/// asyncResolver.resolveAsync(); // Future<int>
|
|
||||||
/// ```
|
|
||||||
class InstanceResolver<T> implements BindingResolver<T> {
|
|
||||||
final Instance<T> _instance;
|
|
||||||
|
|
||||||
/// Wraps the given instance (sync or async) in a resolver.
|
SyncInstanceResolver(this._instance);
|
||||||
InstanceResolver(this._instance);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
T resolveSync([_]) {
|
T resolveSync([_]) => _instance;
|
||||||
if (_instance is T) return _instance;
|
|
||||||
throw StateError(
|
|
||||||
'Instance $_instance is Future; '
|
|
||||||
'use resolveAsync() instead',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<T> resolveAsync([_]) {
|
Future<T> resolveAsync([_]) => Future<T>.value(_instance);
|
||||||
if (_instance is Future<T>) return _instance;
|
|
||||||
return Future.value(_instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void toSingleton() {}
|
void toSingleton() {}
|
||||||
@@ -101,63 +52,31 @@ class InstanceResolver<T> implements BindingResolver<T> {
|
|||||||
bool get isSingleton => true;
|
bool get isSingleton => true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolver for provider functions (sync/async/factory), with optional singleton caching.
|
/// Resolver for a pre-built async instance ([Future]).
|
||||||
/// Used for [Binding.toProvide], [Binding.toProvideWithParams], [Binding.singleton].
|
class AsyncInstanceResolver<T> implements BindingResolver<T> {
|
||||||
///
|
final Future<T> _instance;
|
||||||
/// Examples:
|
|
||||||
/// ```dart
|
|
||||||
/// // No param, sync:
|
|
||||||
/// var r = ProviderResolver((_) => 5, withParams: false);
|
|
||||||
/// r.resolveSync(); // == 5
|
|
||||||
/// // With param:
|
|
||||||
/// var rp = ProviderResolver((p) => p * 2, withParams: true);
|
|
||||||
/// rp.resolveSync(2); // == 4
|
|
||||||
/// // Singleton:
|
|
||||||
/// r.toSingleton();
|
|
||||||
/// // Async:
|
|
||||||
/// var ra = ProviderResolver((_) async => await Future.value(10), withParams: false);
|
|
||||||
/// await ra.resolveAsync(); // == 10
|
|
||||||
/// ```
|
|
||||||
class ProviderResolver<T> implements BindingResolver<T> {
|
|
||||||
final ProviderWithParams<T> _provider;
|
|
||||||
final bool _withParams;
|
|
||||||
|
|
||||||
FutureOr<T>? _cache;
|
AsyncInstanceResolver(this._instance);
|
||||||
|
|
||||||
|
@override
|
||||||
|
T resolveSync([_]) {
|
||||||
|
throw StateError('Instance is a Future; use resolveAsync() instead');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<T> resolveAsync([_]) => _instance;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void toSingleton() {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isSingleton => true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Base class for provider-based resolvers with singleton flag support.
|
||||||
|
abstract class _BaseProviderResolver<T> implements BindingResolver<T> {
|
||||||
bool _singleton = false;
|
bool _singleton = false;
|
||||||
|
|
||||||
/// Creates a resolver from [provider], optionally accepting dynamic params.
|
|
||||||
ProviderResolver(
|
|
||||||
ProviderWithParams<T> provider, {
|
|
||||||
required bool withParams,
|
|
||||||
}) : _provider = provider,
|
|
||||||
_withParams = withParams;
|
|
||||||
|
|
||||||
@override
|
|
||||||
T resolveSync([dynamic params]) {
|
|
||||||
_checkParams(params);
|
|
||||||
final result = _cache ?? _provider(params);
|
|
||||||
if (result is T) {
|
|
||||||
if (_singleton) {
|
|
||||||
_cache ??= result;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
throw StateError(
|
|
||||||
'Provider [$_provider] return Future<$T>. Use resolveAsync() instead.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<T> resolveAsync([dynamic params]) {
|
|
||||||
_checkParams(params);
|
|
||||||
final result = _cache ?? _provider(params);
|
|
||||||
final target = result is Future<T> ? result : Future<T>.value(result);
|
|
||||||
if (_singleton) {
|
|
||||||
_cache ??= target;
|
|
||||||
}
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void toSingleton() {
|
void toSingleton() {
|
||||||
_singleton = true;
|
_singleton = true;
|
||||||
@@ -165,13 +84,281 @@ class ProviderResolver<T> implements BindingResolver<T> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool get isSingleton => _singleton;
|
bool get isSingleton => _singleton;
|
||||||
|
}
|
||||||
|
|
||||||
/// Throws if params required but not supplied.
|
/// Resolves [Binding.toProvide] with a sync `T Function()` provider.
|
||||||
void _checkParams(dynamic params) {
|
class SyncProviderResolver<T> extends _BaseProviderResolver<T> {
|
||||||
if (_withParams && params == null) {
|
final Provider<T> _provider;
|
||||||
throw StateError(
|
Object? _cached;
|
||||||
'[$T] Params is null. Maybe you forgot to pass it?',
|
bool _isCached = false;
|
||||||
);
|
|
||||||
|
SyncProviderResolver(this._provider);
|
||||||
|
|
||||||
|
@override
|
||||||
|
T resolveSync([_]) {
|
||||||
|
if (_singleton && _isCached) {
|
||||||
|
return _cached as T;
|
||||||
}
|
}
|
||||||
|
final result = _provider();
|
||||||
|
if (_singleton) {
|
||||||
|
_cached = result;
|
||||||
|
_isCached = true;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<T> resolveAsync([_]) => Future<T>.value(resolveSync());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolves [Binding.toProvideWithParams] with a sync `T Function(dynamic)` provider.
|
||||||
|
class SyncProviderWithParamsResolver<T> extends _BaseProviderResolver<T> {
|
||||||
|
final ProviderWithParams<T> _provider;
|
||||||
|
Object? _cached;
|
||||||
|
bool _isCached = false;
|
||||||
|
|
||||||
|
SyncProviderWithParamsResolver(this._provider);
|
||||||
|
|
||||||
|
@override
|
||||||
|
T resolveSync([dynamic params]) {
|
||||||
|
if (_singleton && _isCached) {
|
||||||
|
return _cached as T;
|
||||||
|
}
|
||||||
|
if (params == null) {
|
||||||
|
throw StateError('[$T] Params is null. Maybe you forgot to pass it?');
|
||||||
|
}
|
||||||
|
final result = _provider(params);
|
||||||
|
if (_singleton) {
|
||||||
|
_cached = result;
|
||||||
|
_isCached = true;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<T> resolveAsync([dynamic params]) =>
|
||||||
|
Future<T>.value(resolveSync(params));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolves [Binding.toProvide] with an async `Future<T> Function()` provider.
|
||||||
|
class AsyncProviderResolver<T> extends _BaseProviderResolver<T> {
|
||||||
|
final AsyncProvider<T> _provider;
|
||||||
|
Future<T>? _cache;
|
||||||
|
bool _isCached = false;
|
||||||
|
|
||||||
|
AsyncProviderResolver(this._provider);
|
||||||
|
|
||||||
|
@override
|
||||||
|
T resolveSync([_]) {
|
||||||
|
throw StateError(
|
||||||
|
'Provider returns Future<$T>. Use resolveAsync() instead.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<T> resolveAsync([_]) {
|
||||||
|
if (_singleton && _isCached) {
|
||||||
|
return _cache!;
|
||||||
|
}
|
||||||
|
final result = _provider();
|
||||||
|
if (_singleton) {
|
||||||
|
_cache = result;
|
||||||
|
_isCached = true;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolves [Binding.toProvideWithParams] with an async `Future<T> Function(dynamic)` provider.
|
||||||
|
class AsyncProviderWithParamsResolver<T> extends _BaseProviderResolver<T> {
|
||||||
|
final AsyncProviderWithParams<T> _provider;
|
||||||
|
Future<T>? _cache;
|
||||||
|
bool _isCached = false;
|
||||||
|
|
||||||
|
AsyncProviderWithParamsResolver(this._provider);
|
||||||
|
|
||||||
|
@override
|
||||||
|
T resolveSync([_]) {
|
||||||
|
throw StateError(
|
||||||
|
'Provider returns Future<$T>. Use resolveAsync() instead.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<T> resolveAsync([dynamic params]) {
|
||||||
|
if (_singleton && _isCached) {
|
||||||
|
return _cache!;
|
||||||
|
}
|
||||||
|
if (params == null) {
|
||||||
|
throw StateError('[$T] Params is null. Maybe you forgot to pass it?');
|
||||||
|
}
|
||||||
|
final result = _provider(params);
|
||||||
|
if (_singleton) {
|
||||||
|
_cache = result;
|
||||||
|
_isCached = true;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fallback resolver for `FutureOr<T> Function()` providers whose static type
|
||||||
|
/// is not known at compile time. Detects sync vs async at resolve time.
|
||||||
|
class FutureOrProviderResolver<T> extends _BaseProviderResolver<T> {
|
||||||
|
final FutureOr<T> Function() _provider;
|
||||||
|
Object? _cached;
|
||||||
|
bool _isCached = false;
|
||||||
|
|
||||||
|
FutureOrProviderResolver(this._provider);
|
||||||
|
|
||||||
|
@override
|
||||||
|
T resolveSync([_]) {
|
||||||
|
if (_singleton && _isCached) {
|
||||||
|
final cached = _cached;
|
||||||
|
if (cached is Future<T>) {
|
||||||
|
throw StateError(
|
||||||
|
'Provider returns Future<$T>. Use resolveAsync() instead.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return cached as T;
|
||||||
|
}
|
||||||
|
final result = _provider();
|
||||||
|
if (result is Future<T>) {
|
||||||
|
throw StateError(
|
||||||
|
'Provider returns Future<$T>. Use resolveAsync() instead.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (_singleton) {
|
||||||
|
_cached = result;
|
||||||
|
_isCached = true;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<T> resolveAsync([_]) {
|
||||||
|
if (_singleton && _isCached) {
|
||||||
|
final cached = _cached;
|
||||||
|
if (cached is Future<T>) return cached;
|
||||||
|
return Future<T>.value(cached as T);
|
||||||
|
}
|
||||||
|
final result = _provider();
|
||||||
|
if (_singleton) {
|
||||||
|
_cached = result;
|
||||||
|
_isCached = true;
|
||||||
|
}
|
||||||
|
if (result is Future<T>) return result;
|
||||||
|
return Future<T>.value(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fallback resolver for `FutureOr<T> Function(dynamic)` providers with params
|
||||||
|
/// whose static type is not known at compile time.
|
||||||
|
class FutureOrProviderWithParamsResolver<T> extends _BaseProviderResolver<T> {
|
||||||
|
final FutureOr<T> Function(dynamic) _provider;
|
||||||
|
Object? _cached;
|
||||||
|
bool _isCached = false;
|
||||||
|
|
||||||
|
FutureOrProviderWithParamsResolver(this._provider);
|
||||||
|
|
||||||
|
@override
|
||||||
|
T resolveSync([dynamic params]) {
|
||||||
|
if (_singleton && _isCached) {
|
||||||
|
final cached = _cached;
|
||||||
|
if (cached is Future<T>) {
|
||||||
|
throw StateError(
|
||||||
|
'Provider returns Future<$T>. Use resolveAsync() instead.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return cached as T;
|
||||||
|
}
|
||||||
|
if (params == null) {
|
||||||
|
throw StateError('[$T] Params is null. Maybe you forgot to pass it?');
|
||||||
|
}
|
||||||
|
final result = _provider(params);
|
||||||
|
if (result is Future<T>) {
|
||||||
|
throw StateError(
|
||||||
|
'Provider returns Future<$T>. Use resolveAsync() instead.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (_singleton) {
|
||||||
|
_cached = result;
|
||||||
|
_isCached = true;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<T> resolveAsync([dynamic params]) {
|
||||||
|
if (_singleton && _isCached) {
|
||||||
|
final cached = _cached;
|
||||||
|
if (cached is Future<T>) return cached;
|
||||||
|
return Future<T>.value(cached as T);
|
||||||
|
}
|
||||||
|
if (params == null) {
|
||||||
|
throw StateError('[$T] Params is null. Maybe you forgot to pass it?');
|
||||||
|
}
|
||||||
|
final result = _provider(params);
|
||||||
|
if (_singleton) {
|
||||||
|
_cached = result;
|
||||||
|
_isCached = true;
|
||||||
|
}
|
||||||
|
if (result is Future<T>) return result;
|
||||||
|
return Future<T>.value(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Factory for creating instance resolvers (sync or async).
|
||||||
|
class InstanceResolver {
|
||||||
|
static BindingResolver<T> create<T>(FutureOr<T> instance) {
|
||||||
|
if (instance is Future<T>) {
|
||||||
|
return AsyncInstanceResolver<T>(instance);
|
||||||
|
}
|
||||||
|
return SyncInstanceResolver<T>(instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Factory for creating the correct provider resolver based on the
|
||||||
|
/// provider's static return type, avoiding runtime checks in fast paths.
|
||||||
|
class ProviderResolver {
|
||||||
|
static BindingResolver<T> create<T>(FutureOr<T> Function() provider) {
|
||||||
|
if (provider is T Function()) {
|
||||||
|
return SyncProviderResolver<T>(provider);
|
||||||
|
}
|
||||||
|
if (provider is Future<T> Function()) {
|
||||||
|
return AsyncProviderResolver<T>(provider);
|
||||||
|
}
|
||||||
|
return FutureOrProviderResolver<T>(provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
static BindingResolver<T> createWithParams<T>(
|
||||||
|
FutureOr<T> Function(dynamic) provider) {
|
||||||
|
if (provider is T Function(dynamic)) {
|
||||||
|
return SyncProviderWithParamsResolver<T>(provider);
|
||||||
|
}
|
||||||
|
if (provider is Future<T> Function(dynamic)) {
|
||||||
|
return AsyncProviderWithParamsResolver<T>(provider);
|
||||||
|
}
|
||||||
|
return FutureOrProviderWithParamsResolver<T>(provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Explicit sync resolver without parameters.
|
||||||
|
static BindingResolver<T> sync<T>(Provider<T> provider) {
|
||||||
|
return SyncProviderResolver<T>(provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Explicit sync resolver with parameters.
|
||||||
|
static BindingResolver<T> syncWithParams<T>(ProviderWithParams<T> provider) {
|
||||||
|
return SyncProviderWithParamsResolver<T>(provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Explicit async resolver without parameters.
|
||||||
|
static BindingResolver<T> async<T>(AsyncProvider<T> provider) {
|
||||||
|
return AsyncProviderResolver<T>(provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Explicit async resolver with parameters.
|
||||||
|
static BindingResolver<T> asyncWithParams<T>(
|
||||||
|
AsyncProviderWithParams<T> provider) {
|
||||||
|
return AsyncProviderWithParamsResolver<T>(provider);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:cherrypick/cherrypick.dart';
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
@@ -12,7 +14,8 @@ void main() {
|
|||||||
|
|
||||||
test('Sets mode to instance', () {
|
test('Sets mode to instance', () {
|
||||||
final binding = Binding<int>().toInstance(5);
|
final binding = Binding<int>().toInstance(5);
|
||||||
expect(binding.resolver, isA<InstanceResolver<int>>());
|
expect(binding.resolver, isA<BindingResolver<int>>());
|
||||||
|
expect(binding.resolver, isA<SyncInstanceResolver<int>>());
|
||||||
});
|
});
|
||||||
|
|
||||||
test('isSingleton is true', () {
|
test('isSingleton is true', () {
|
||||||
@@ -34,7 +37,8 @@ void main() {
|
|||||||
|
|
||||||
test('Sets mode to instance', () {
|
test('Sets mode to instance', () {
|
||||||
final binding = Binding<int>().withName('n').toInstance(5);
|
final binding = Binding<int>().withName('n').toInstance(5);
|
||||||
expect(binding.resolver, isA<InstanceResolver<int>>());
|
expect(binding.resolver, isA<BindingResolver<int>>());
|
||||||
|
expect(binding.resolver, isA<SyncInstanceResolver<int>>());
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Sets key', () {
|
test('Sets key', () {
|
||||||
@@ -73,7 +77,8 @@ void main() {
|
|||||||
|
|
||||||
test('Sets mode to instance', () {
|
test('Sets mode to instance', () {
|
||||||
final binding = Binding<int>().toInstance(Future.value(5));
|
final binding = Binding<int>().toInstance(Future.value(5));
|
||||||
expect(binding.resolver, isA<InstanceResolver<int>>());
|
expect(binding.resolver, isA<BindingResolver<int>>());
|
||||||
|
expect(binding.resolver, isA<AsyncInstanceResolver<int>>());
|
||||||
});
|
});
|
||||||
|
|
||||||
test('isSingleton is true after toInstanceAsync', () {
|
test('isSingleton is true after toInstanceAsync', () {
|
||||||
@@ -107,7 +112,8 @@ void main() {
|
|||||||
|
|
||||||
test('Sets mode to providerInstance', () {
|
test('Sets mode to providerInstance', () {
|
||||||
final binding = Binding<int>().toProvide(() => 5);
|
final binding = Binding<int>().toProvide(() => 5);
|
||||||
expect(binding.resolver, isA<ProviderResolver<int>>());
|
expect(binding.resolver, isA<BindingResolver<int>>());
|
||||||
|
expect(binding.resolveSync(), 5);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('isSingleton is false by default', () {
|
test('isSingleton is false by default', () {
|
||||||
@@ -129,7 +135,8 @@ void main() {
|
|||||||
|
|
||||||
test('Sets mode to providerInstance', () {
|
test('Sets mode to providerInstance', () {
|
||||||
final binding = Binding<int>().withName('n').toProvide(() => 5);
|
final binding = Binding<int>().withName('n').toProvide(() => 5);
|
||||||
expect(binding.resolver, isA<ProviderResolver<int>>());
|
expect(binding.resolver, isA<BindingResolver<int>>());
|
||||||
|
expect(binding.resolveSync(), 5);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Sets key', () {
|
test('Sets key', () {
|
||||||
@@ -166,6 +173,43 @@ void main() {
|
|||||||
.toProvideWithParams((param) async => 5 + (param as int));
|
.toProvideWithParams((param) async => 5 + (param as int));
|
||||||
expect(await binding.resolveAsync(3), 8);
|
expect(await binding.resolveAsync(3), 8);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Resolves toProvideAsync value', () async {
|
||||||
|
final binding = Binding<int>().toProvideAsync(() async => 5);
|
||||||
|
expect(await binding.resolveAsync(), 5);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('toProvideAsync singleton caches instance', () async {
|
||||||
|
int counter = 0;
|
||||||
|
final binding = Binding<int>().toProvideAsync(() async {
|
||||||
|
counter++;
|
||||||
|
return counter;
|
||||||
|
}).singleton();
|
||||||
|
|
||||||
|
final first = await binding.resolveAsync();
|
||||||
|
final second = await binding.resolveAsync();
|
||||||
|
expect(first, equals(second));
|
||||||
|
expect(counter, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Resolves toProvideAsyncWithParams value', () async {
|
||||||
|
final binding = Binding<int>()
|
||||||
|
.toProvideAsyncWithParams((param) async => 5 + (param as int));
|
||||||
|
expect(await binding.resolveAsync(3), 8);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('toProvideAsyncWithParams singleton caches instance', () async {
|
||||||
|
int counter = 0;
|
||||||
|
final binding = Binding<int>().toProvideAsyncWithParams((param) async {
|
||||||
|
counter++;
|
||||||
|
return counter + (param as int);
|
||||||
|
}).singleton();
|
||||||
|
|
||||||
|
final first = await binding.resolveAsync(10);
|
||||||
|
final second = await binding.resolveAsync(20);
|
||||||
|
expect(first, equals(second));
|
||||||
|
expect(counter, 1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- Singleton provider binding ---
|
// --- Singleton provider binding ---
|
||||||
@@ -232,6 +276,103 @@ void main() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// --- FutureOr provider resolver (lazy detection, no eager side-effects) ---
|
||||||
|
group('FutureOr Provider Resolver', () {
|
||||||
|
test('Does not call provider at binding creation', () {
|
||||||
|
int calls = 0;
|
||||||
|
FutureOr<int> provider() {
|
||||||
|
calls++;
|
||||||
|
return 42;
|
||||||
|
}
|
||||||
|
|
||||||
|
final binding = Binding<int>().toProvide(provider);
|
||||||
|
expect(calls, 0);
|
||||||
|
expect(binding.resolveSync(), 42);
|
||||||
|
expect(calls, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Sync FutureOr provider resolves via resolveSync', () {
|
||||||
|
final binding = Binding<int>().toProvide(() => 7);
|
||||||
|
expect(binding.resolveSync(), 7);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Async FutureOr provider throws on resolveSync', () {
|
||||||
|
final binding =
|
||||||
|
Binding<int>().toProvide(() async => 7);
|
||||||
|
expect(() => binding.resolveSync(), throwsStateError);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Async FutureOr provider resolves via resolveAsync', () async {
|
||||||
|
final binding =
|
||||||
|
Binding<int>().toProvide(() async => 7);
|
||||||
|
expect(await binding.resolveAsync(), 7);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('FutureOr provider with params does not call provider at creation',
|
||||||
|
() {
|
||||||
|
int calls = 0;
|
||||||
|
FutureOr<int> provider(dynamic p) {
|
||||||
|
calls++;
|
||||||
|
return (p as int) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
final binding = Binding<int>().toProvideWithParams(provider);
|
||||||
|
expect(calls, 0);
|
||||||
|
expect(binding.resolveSync(5), 6);
|
||||||
|
expect(calls, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Async FutureOr provider with params throws on resolveSync', () {
|
||||||
|
final binding = Binding<int>()
|
||||||
|
.toProvideWithParams((p) async => (p as int) + 1);
|
||||||
|
expect(() => binding.resolveSync(5), throwsStateError);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Async FutureOr provider with params resolves via resolveAsync',
|
||||||
|
() async {
|
||||||
|
final binding = Binding<int>()
|
||||||
|
.toProvideWithParams((p) async => (p as int) + 1);
|
||||||
|
expect(await binding.resolveAsync(5), 6);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('FutureOr singleton caches sync result', () {
|
||||||
|
int calls = 0;
|
||||||
|
final binding = Binding<int>().toProvide(() {
|
||||||
|
calls++;
|
||||||
|
return 99;
|
||||||
|
}).singleton();
|
||||||
|
|
||||||
|
expect(binding.resolveSync(), 99);
|
||||||
|
expect(binding.resolveSync(), 99);
|
||||||
|
expect(calls, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('FutureOr singleton caches async result', () async {
|
||||||
|
int calls = 0;
|
||||||
|
final binding = Binding<int>().toProvide(() async {
|
||||||
|
calls++;
|
||||||
|
return 99;
|
||||||
|
}).singleton();
|
||||||
|
|
||||||
|
expect(await binding.resolveAsync(), 99);
|
||||||
|
expect(await binding.resolveAsync(), 99);
|
||||||
|
expect(calls, 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- InstanceResolver factory ---
|
||||||
|
group('InstanceResolver.create', () {
|
||||||
|
test('Returns SyncInstanceResolver for sync value', () {
|
||||||
|
final binding = Binding<int>().toInstance(5);
|
||||||
|
expect(binding.resolver, isA<SyncInstanceResolver<int>>());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Returns AsyncInstanceResolver for Future value', () {
|
||||||
|
final binding = Binding<int>().toInstance(Future.value(5));
|
||||||
|
expect(binding.resolver, isA<AsyncInstanceResolver<int>>());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// --- WithName / Named binding, isNamed, edge-cases ---
|
// --- WithName / Named binding, isNamed, edge-cases ---
|
||||||
group('Named binding & helpers', () {
|
group('Named binding & helpers', () {
|
||||||
test('withName sets isNamed true and stores name', () {
|
test('withName sets isNamed true and stores name', () {
|
||||||
|
|||||||
Reference in New Issue
Block a user