From ce2e770cbed07e7807122b5ecab0e12d01d0c87b Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Mon, 8 Sep 2025 14:07:48 +0300 Subject: [PATCH] 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 --- website/docs/core-concepts/binding.md | 100 +++++++++++++++--- .../current/core-concepts/binding.md | 100 +++++++++++++++--- website/package.json | 3 +- 3 files changed, 168 insertions(+), 35 deletions(-) diff --git a/website/docs/core-concepts/binding.md b/website/docs/core-concepts/binding.md index a12727f..b70d5d6 100644 --- a/website/docs/core-concepts/binding.md +++ b/website/docs/core-concepts/binding.md @@ -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().toInstance("Hello world"); +void builder(Scope scope) { + // Provide a direct instance + bind().toInstance("Hello world"); -// Provide an async direct instance -Binding().toInstanceAsync(Future.value("Hello world")); + // Provide an async direct instance + bind().toInstanceAsync(Future.value("Hello world")); -// Provide a lazy sync instance using a factory -Binding().toProvide(() => "Hello world"); + // Provide a lazy sync instance using a factory + bind().toProvide(() => "Hello world"); -// Provide a lazy async instance using a factory -Binding().toProvideAsync(() async => "Hello async world"); + // Provide a lazy async instance using a factory + bind().toProvideAsync(() async => "Hello async world"); -// Provide an instance with dynamic parameters (sync) -Binding().toProvideWithParams((params) => "Hello $params"); + // Provide an instance with dynamic parameters (sync) + bind().toProvideWithParams((params) => "Hello $params"); -// Provide an instance with dynamic parameters (async) -Binding().toProvideAsyncWithParams((params) async => "Hello $params"); + // Provide an instance with dynamic parameters (async) + bind().toProvideAsyncWithParams((params) async => "Hello $params"); -// Named instance for retrieval by name -Binding().toProvide(() => "Hello world").withName("my_string"); + // Named instance for retrieval by name + bind().toProvide(() => "Hello world").withName("my_string"); -// Mark as singleton (only one instance within the scope) -Binding().toProvide(() => "Hello world").singleton(); + // Mark as singleton (only one instance within the scope) + bind().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()` 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().toInstance(a); +> bind().toInstance(b); +> bind().toInstance(c); +> } +> ``` +> +> **Wrong:** +> ```dart +> void builder(Scope scope) { +> bind().toInstance(A()); +> // Error! At this point, A is not registered yet. +> bind().toInstance(B(scope.resolve())); +> } +> ``` +> +> **Wrong:** +> ```dart +> void builder(Scope scope) { +> bind().toProvide(() => A()); +> // Error! At this point, A is not registered yet. +> bind().toInstance(B(scope.resolve())); +> } +> ``` +> +> **Note:** This limitation applies **only** to `toInstance`. With `toProvide`/`toProvideAsync` and similar providers, you can safely use `scope.resolve()` 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(params: ...)` will use its parameters; every subsequent call (regardless of params) will return the same (cached) instance. + > + > **Example:** + > ```dart + > bind().toProvideWithParams((params) => Service(params)).singleton(); + > final a = scope.resolve(params: 1); // creates Service(1) + > final b = scope.resolve(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. diff --git a/website/i18n/ru/docusaurus-plugin-content-docs/current/core-concepts/binding.md b/website/i18n/ru/docusaurus-plugin-content-docs/current/core-concepts/binding.md index 23ed1dd..035f591 100644 --- a/website/i18n/ru/docusaurus-plugin-content-docs/current/core-concepts/binding.md +++ b/website/i18n/ru/docusaurus-plugin-content-docs/current/core-concepts/binding.md @@ -12,30 +12,96 @@ sidebar_position: 1 * Именованные экземпляры для получения по строковому ключу * Необязательное управление жизненным циклом синглтона -## Пример +#### Пример ```dart -// Прямое создание экземпляра -Binding().toInstance("Hello world"); +void builder(Scope scope) { + // Прямое предоставление экземпляра + bind().toInstance("Hello world"); -// Асинхронное создание экземпляра -Binding().toInstanceAsync(Future.value("Hello world")); + // Асинхронное предоставление экземпляра + bind().toInstanceAsync(Future.value("Hello world")); -// Ленивое создание экземпляра через фабрику (sync) -Binding().toProvide(() => "Hello world"); + // Ленивое создание синхронного экземпляра через фабрику + bind().toProvide(() => "Hello world"); -// Ленивое создание экземпляра через фабрику (async) -Binding().toProvideAsync(() async => "Hello async world"); + // Ленивое создание асинхронного экземпляра через фабрику + bind().toProvideAsync(() async => "Hello async world"); -// Экземпляр с параметрами (sync) -Binding().toProvideWithParams((params) => "Hello $params"); + // Предоставление экземпляра с динамическими параметрами (синхронно) + bind().toProvideWithParams((params) => "Hello $params"); -// Экземпляр с параметрами (async) -Binding().toProvideAsyncWithParams((params) async => "Hello $params"); + // Предоставление экземпляра с динамическими параметрами (асинхронно) + bind().toProvideAsyncWithParams((params) async => "Hello $params"); -// Именованный экземпляр для получения по имени -Binding().toProvide(() => "Hello world").withName("my_string"); + // Именованный экземпляр для получения по имени + bind().toProvide(() => "Hello world").withName("my_string"); -// Синглтон (один экземпляр внутри скоупа) -Binding().toProvide(() => "Hello world").singleton(); + // Пометить как синглтон (только один экземпляр в пределах скоупа) + bind().toProvide(() => "Hello world").singleton(); +} ``` + +> ⚠️ **Важное примечание об использовании `toInstance` в `builder` модуля:** +> +> Если вы регистрируете цепочку зависимостей через `toInstance` внутри `builder` модуля, **не вызывайте** `scope.resolve()` для типов, которые также регистрируются в том же 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().toInstance(a); +> bind().toInstance(b); +> bind().toInstance(c); +> } +> ``` +> +> **Неправильно:** +> ```dart +> void builder(Scope scope) { +> bind().toInstance(A()); +> // Ошибка! В этот момент A еще не зарегистрирован. +> bind().toInstance(B(scope.resolve())); +> } +> ``` +> +> **Неправильно:** +> ```dart +> void builder(Scope scope) { +> bind().toProvide(() => A()); +> // Ошибка! В этот момент A еще не зарегистрирован. +> bind().toInstance(B(scope.resolve())); +> } +> ``` +> +> **Примечание:** Это ограничение применяется **только** к `toInstance`. С `toProvide`/`toProvideAsync` и подобными провайдерами вы можете безопасно использовать `scope.resolve()` внутри builder. + + + > ⚠️ **Особое примечание относительно `.singleton()` с `toProvideWithParams()` / `toProvideAsyncWithParams()`:** + > + > Если вы объявляете привязку с помощью `.toProvideWithParams(...)` (или его асинхронного варианта) и затем добавляете `.singleton()`, только **самый первый** вызов `resolve(params: ...)` использует свои параметры; каждый последующий вызов (независимо от параметров) вернет тот же (кешированный) экземпляр. + > + > **Пример:** + > ```dart + > bind().toProvideWithParams((params) => Service(params)).singleton(); + > final a = scope.resolve(params: 1); // создает Service(1) + > final b = scope.resolve(params: 2); // возвращает Service(1) + > print(identical(a, b)); // true + > ``` + > + > Используйте этот паттерн только когда хотите получить "главный" синглтон. Если вы ожидаете новый экземпляр для каждого набора параметров, **не используйте** `.singleton()` с параметризованными провайдерами. + + +> ℹ️ **Примечание о `.singleton()` и `.toInstance()`:** +> +> Вызов `.singleton()` после `.toInstance()` **не** меняет поведение привязки: объект, переданный с `toInstance()`, уже является единым, постоянным экземпляром, который всегда будет возвращаться при каждом resolve. +> +> Не обязательно использовать `.singleton()` с существующим объектом — этот вызов не имеет эффекта. +> +> `.singleton()` имеет смысл только с провайдерами (такими как `toProvide`/`toProvideAsync`), чтобы гарантировать создание только одного экземпляра фабрикой. diff --git a/website/package.json b/website/package.json index c6f20eb..0d2ecc9 100644 --- a/website/package.json +++ b/website/package.json @@ -43,5 +43,6 @@ }, "engines": { "node": ">=18.0" - } + }, + "packageManager": "yarn@1.22.19+sha1.4ba7fc5c6e704fce2066ecbfb0b0d8976fe62447" }