From b5b672765eff0cd8313107b281d477d991f039f3 Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Mon, 8 Sep 2025 10:18:19 +0300 Subject: [PATCH] docs(binding,docs): explain .singleton() + parametric provider behavior - Add an explicit warning and usage examples for .singleton() combined with toProvideWithParams/toProvideAsyncWithParams: - in API doc-comment for singleton() in binding.dart, - in README.md and both full tutorials (EN/RU). - Show correct and incorrect usage/pitfalls for parameterized providers and singleton. - Help users avoid unintended singleton caching when using providers with parameters. - Motivation: Prevent common confusion, make advanced DI scenarios safer and more obvious. --- cherrypick/README.md | 16 ++++++++++++++++ cherrypick/lib/src/binding.dart | 17 +++++++++++++++++ doc/full_tutorial_en.md | 20 ++++++++++++++++++++ doc/full_tutorial_ru.md | 20 ++++++++++++++++++++ 4 files changed, 73 insertions(+) diff --git a/cherrypick/README.md b/cherrypick/README.md index c9fb1bc..ba863cd 100644 --- a/cherrypick/README.md +++ b/cherrypick/README.md @@ -169,6 +169,22 @@ void builder(Scope scope) { > > **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. + + ### Module A **Module** is a logical collection point for bindings, designed for grouping and initializing related dependencies. Implement the `builder` method to define how dependencies should be bound within the scope. diff --git a/cherrypick/lib/src/binding.dart b/cherrypick/lib/src/binding.dart index c29011b..e0c2c65 100644 --- a/cherrypick/lib/src/binding.dart +++ b/cherrypick/lib/src/binding.dart @@ -241,6 +241,23 @@ class Binding { /// ```dart /// bind().toProvide(() => MyApi()).singleton(); /// ``` + /// + /// --- + /// + /// ⚠️ **Special note: Behavior with parametric providers (`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 if you want a master singleton. If you expect a new instance per params, **do not** use `.singleton()` on parameterized providers. Binding singleton() { _resolver?.toSingleton(); return this; diff --git a/doc/full_tutorial_en.md b/doc/full_tutorial_en.md index 9d57966..d71f7d3 100644 --- a/doc/full_tutorial_en.md +++ b/doc/full_tutorial_en.md @@ -118,6 +118,26 @@ bind().toProvideWithParams((userId) => UserService(userId)); final userService = scope.resolve(params: '123'); ``` +> ⚠️ **Special note on using `.singleton()` after `toProvideWithParams` or `toProvideAsyncWithParams`:** +> +> If you declare a binding using `.toProvideWithParams((params) => ...)` (or its async variant) and then call `.singleton()`, the DI container will create and cache **only one instance** on the first `resolve` call—with the initial parameters. All subsequent calls to `resolve(params: ...)` will return that same (cached) instance, **regardless of the new parameters**. +> +> **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 +> ``` +> +> In other words: +> - The provider function receives parameters only on its first call, +> - After that, no matter what parameters are passed, the same instance is always returned. +> +> **Recommendation:** +> Use `.singleton()` with parameterized providers only if you are sure all parameters should always be identical, or you intentionally want a “master” instance. Otherwise, omit `.singleton()` to ensure a new object is constructed for every unique `params` value. + --- ## Scope management: dependency hierarchy diff --git a/doc/full_tutorial_ru.md b/doc/full_tutorial_ru.md index 67c01d3..2590661 100644 --- a/doc/full_tutorial_ru.md +++ b/doc/full_tutorial_ru.md @@ -119,6 +119,26 @@ bind().toProvideWithParams((userId) => UserService(userId)); final userService = scope.resolve(params: '123'); ``` +> ⚠️ **Особенности использования `.singleton()` после `toProvideWithParams` или `toProvideAsyncWithParams`:** +> +> Если вы объявляете биндинг через `.toProvideWithParams((params) => ...)` (или асинхронный вариант) и затем вызываете `.singleton()`, DI-контейнер создаст и закэширует **только один экземпляр** при первом вызове `resolve` — с первыми переданными параметрами. Все последующие вызовы `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()`, чтобы каждый вызов с новыми parameters создавал новый объект. + --- ## Управление Scope'ами: иерархия зависимостей