docs: add important warnings about toInstance limitations and singleton behavior with params

- Add detailed warning about toInstance usage restrictions in module builders
- Explain singleton behavior with parameterized providers
- Clarify singleton() usage with toInstance() calls
- Update both English and Russian documentation versions
This commit is contained in:
Sergey Penkovsky
2025-09-08 14:07:48 +03:00
parent 7f5f5c4064
commit ce2e770cbe
3 changed files with 168 additions and 35 deletions

View File

@@ -12,30 +12,96 @@ A **Binding** acts as a configuration for how to create or provide a particular
* Named instances for resolving by string key
* Optional singleton lifecycle
## Example
#### Example
```dart
// Provide a direct instance
Binding<String>().toInstance("Hello world");
void builder(Scope scope) {
// Provide a direct instance
bind<String>().toInstance("Hello world");
// Provide an async direct instance
Binding<String>().toInstanceAsync(Future.value("Hello world"));
// Provide an async direct instance
bind<String>().toInstanceAsync(Future.value("Hello world"));
// Provide a lazy sync instance using a factory
Binding<String>().toProvide(() => "Hello world");
// Provide a lazy sync instance using a factory
bind<String>().toProvide(() => "Hello world");
// Provide a lazy async instance using a factory
Binding<String>().toProvideAsync(() async => "Hello async world");
// Provide a lazy async instance using a factory
bind<String>().toProvideAsync(() async => "Hello async world");
// Provide an instance with dynamic parameters (sync)
Binding<String>().toProvideWithParams((params) => "Hello $params");
// Provide an instance with dynamic parameters (sync)
bind<String>().toProvideWithParams((params) => "Hello $params");
// Provide an instance with dynamic parameters (async)
Binding<String>().toProvideAsyncWithParams((params) async => "Hello $params");
// Provide an instance with dynamic parameters (async)
bind<String>().toProvideAsyncWithParams((params) async => "Hello $params");
// Named instance for retrieval by name
Binding<String>().toProvide(() => "Hello world").withName("my_string");
// Named instance for retrieval by name
bind<String>().toProvide(() => "Hello world").withName("my_string");
// Mark as singleton (only one instance within the scope)
Binding<String>().toProvide(() => "Hello world").singleton();
// Mark as singleton (only one instance within the scope)
bind<String>().toProvide(() => "Hello world").singleton();
}
```
> ⚠️ **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`:
>
> ```dart
> void builder(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:**
> ```dart
> void builder(Scope scope) {
> bind<A>().toInstance(A());
> // Error! At this point, A is not registered yet.
> bind<B>().toInstance(B(scope.resolve<A>()));
> }
> ```
>
> **Wrong:**
> ```dart
> void builder(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 first** `resolve<T>(params: ...)` will use its parameters; every subsequent call (regardless of params) will return the same (cached) instance.
>
> **Example:**
> ```dart
> bind<Service>().toProvideWithParams((params) => Service(params)).singleton();
> 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 bindings 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.

View File

@@ -12,30 +12,96 @@ sidebar_position: 1
* Именованные экземпляры для получения по строковому ключу
* Необязательное управление жизненным циклом синглтона
## Пример
#### Пример
```dart
// Прямое создание экземпляра
Binding<String>().toInstance("Hello world");
void builder(Scope scope) {
// Прямое предоставление экземпляра
bind<String>().toInstance("Hello world");
// Асинхронное создание экземпляра
Binding<String>().toInstanceAsync(Future.value("Hello world"));
// Асинхронное предоставление экземпляра
bind<String>().toInstanceAsync(Future.value("Hello world"));
// Ленивое создание экземпляра через фабрику (sync)
Binding<String>().toProvide(() => "Hello world");
// Ленивое создание синхронного экземпляра через фабрику
bind<String>().toProvide(() => "Hello world");
// Ленивое создание экземпляра через фабрику (async)
Binding<String>().toProvideAsync(() async => "Hello async world");
// Ленивое создание асинхронного экземпляра через фабрику
bind<String>().toProvideAsync(() async => "Hello async world");
// Экземпляр с параметрами (sync)
Binding<String>().toProvideWithParams((params) => "Hello $params");
// Предоставление экземпляра с динамическими параметрами (синхронно)
bind<String>().toProvideWithParams((params) => "Hello $params");
// Экземпляр с параметрами (async)
Binding<String>().toProvideAsyncWithParams((params) async => "Hello $params");
// Предоставление экземпляра с динамическими параметрами (асинхронно)
bind<String>().toProvideAsyncWithParams((params) async => "Hello $params");
// Именованный экземпляр для получения по имени
Binding<String>().toProvide(() => "Hello world").withName("my_string");
// Именованный экземпляр для получения по имени
bind<String>().toProvide(() => "Hello world").withName("my_string");
// Синглтон (один экземпляр внутри скоупа)
Binding<String>().toProvide(() => "Hello world").singleton();
// Пометить как синглтон (только один экземпляр в пределах скоупа)
bind<String>().toProvide(() => "Hello world").singleton();
}
```
> ⚠️ **Важное примечание об использовании `toInstance` в `builder` модуля:**
>
> Если вы регистрируете цепочку зависимостей через `toInstance` внутри `builder` модуля, **не вызывайте** `scope.resolve<T>()` для типов, которые также регистрируются в том же builder — в момент их регистрации.
>
> CherryPick инициализирует все привязки в builder последовательно. Зависимости, зарегистрированные ранее, еще не доступны для `resolve` в рамках того же выполнения builder. Попытка разрешить только что зарегистрированные типы приведет к ошибке (`Can't resolve dependency ...`).
>
> **Как делать правильно:**
> Вручную создайте полную цепочку зависимостей перед вызовом `toInstance`:
>
> ```dart
> void builder(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);
> }
> ```
>
> **Неправильно:**
> ```dart
> void builder(Scope scope) {
> bind<A>().toInstance(A());
> // Ошибка! В этот момент A еще не зарегистрирован.
> bind<B>().toInstance(B(scope.resolve<A>()));
> }
> ```
>
> **Неправильно:**
> ```dart
> void builder(Scope scope) {
> bind<A>().toProvide(() => A());
> // Ошибка! В этот момент A еще не зарегистрирован.
> bind<B>().toInstance(B(scope.resolve<A>()));
> }
> ```
>
> **Примечание:** Это ограничение применяется **только** к `toInstance`. С `toProvide`/`toProvideAsync` и подобными провайдерами вы можете безопасно использовать `scope.resolve<T>()` внутри builder.
> ⚠️ **Особое примечание относительно `.singleton()` с `toProvideWithParams()` / `toProvideAsyncWithParams()`:**
>
> Если вы объявляете привязку с помощью `.toProvideWithParams(...)` (или его асинхронного варианта) и затем добавляете `.singleton()`, только **самый первый** вызов `resolve<T>(params: ...)` использует свои параметры; каждый последующий вызов (независимо от параметров) вернет тот же (кешированный) экземпляр.
>
> **Пример:**
> ```dart
> bind<Service>().toProvideWithParams((params) => Service(params)).singleton();
> final a = scope.resolve<Service>(params: 1); // создает Service(1)
> final b = scope.resolve<Service>(params: 2); // возвращает Service(1)
> print(identical(a, b)); // true
> ```
>
> Используйте этот паттерн только когда хотите получить "главный" синглтон. Если вы ожидаете новый экземпляр для каждого набора параметров, **не используйте** `.singleton()` с параметризованными провайдерами.
> **Примечание о `.singleton()` и `.toInstance()`:**
>
> Вызов `.singleton()` после `.toInstance()` **не** меняет поведение привязки: объект, переданный с `toInstance()`, уже является единым, постоянным экземпляром, который всегда будет возвращаться при каждом resolve.
>
> Не обязательно использовать `.singleton()` с существующим объектом — этот вызов не имеет эффекта.
>
> `.singleton()` имеет смысл только с провайдерами (такими как `toProvide`/`toProvideAsync`), чтобы гарантировать создание только одного экземпляра фабрикой.

View File

@@ -43,5 +43,6 @@
},
"engines": {
"node": ">=18.0"
}
},
"packageManager": "yarn@1.22.19+sha1.4ba7fc5c6e704fce2066ecbfb0b0d8976fe62447"
}