Skip to content

Commit 9855125

Browse files
authored
Add solution for late-init dependencies (#23)
Add late initialization providers: FactoryDependency Add option to update ValueDependency's value
1 parent f9bfcd1 commit 9855125

File tree

7 files changed

+160
-18
lines changed

7 files changed

+160
-18
lines changed

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,14 @@ Create `Module` (recommend to use `object`) and extends from `Leviathan` class
4141

4242
Create fields using these functions:
4343

44-
- Use `by instanceOf(keepAlive){/**/}` to create instance dependency
44+
- Use `by instanceOf(keepAlive){ value }` to create instance dependency
4545
- `keepAlive = true` : instance persists across different scopes
4646
- `keepAlive = false`(default): instance is auto-closed when all scopes close
4747
- Use `by factoryOf(useCache)` to create factory dependency
4848
- `useCache = true` (default): caches instances within the same scope
4949
- `useCache = false`: creates new instance on each access
50-
- Use `by valueOf(value)` to create value dependency (returns the same value always)
50+
- Use `by valueOf(value)` to create value dependency (the value may be updated later)
51+
- Use `by providableOf { value }` to create a providable dependency (provider may be updated later)
5152

5253
All functions return a `Dependency<Type>` instance.
5354

@@ -78,6 +79,7 @@ object Module : Leviathan() {
7879
}
7980
val interfaceRepo by instanceOf<SampleInterfaceRepo> { SampleInterfaceRepoImpl() }
8081
val constantValue by valueOf(42)
82+
val providable by providableOf { 34 }
8183
}
8284
```
8385

@@ -107,6 +109,8 @@ fun ComposeWithDI() {
107109
fun foo() {
108110
val scope = DIScope()
109111
val repo1 = Module.autoCloseRepository.injectedIn(scope)
112+
// update providable value
113+
(Module.providable as? ProvidableDependency<Int>)?.provides { 21 }
110114
/*..*/
111115
scope.close()
112116
}

gradle/leviathan.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
[versions]
2-
leviathan = "3.0.0"
2+
leviathan = "3.1.0"

leviathan/api/android/leviathan.api

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@ public final class com/composegears/leviathan/DependencyInitializationScope {
1616
public final fun inject (Lcom/composegears/leviathan/Dependency;)Ljava/lang/Object;
1717
}
1818

19+
public final class com/composegears/leviathan/FactoryDependency : com/composegears/leviathan/Dependency {
20+
public fun injectedIn (Lcom/composegears/leviathan/DIScope;)Ljava/lang/Object;
21+
}
22+
23+
public final class com/composegears/leviathan/InstanceDependency : com/composegears/leviathan/Dependency {
24+
public fun injectedIn (Lcom/composegears/leviathan/DIScope;)Ljava/lang/Object;
25+
}
26+
1927
public abstract class com/composegears/leviathan/Leviathan {
2028
public static final field Companion Lcom/composegears/leviathan/Leviathan$Companion;
2129
public fun <init> ()V
@@ -24,14 +32,20 @@ public abstract class com/composegears/leviathan/Leviathan {
2432
protected static final fun getValue (Lcom/composegears/leviathan/Dependency;Lcom/composegears/leviathan/Leviathan;Lkotlin/reflect/KProperty;)Lcom/composegears/leviathan/Dependency;
2533
protected final fun instanceOf (ZLkotlin/jvm/functions/Function1;)Lcom/composegears/leviathan/Dependency;
2634
public static synthetic fun instanceOf$default (Lcom/composegears/leviathan/Leviathan;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/composegears/leviathan/Dependency;
35+
protected final fun providableOf (Lkotlin/jvm/functions/Function0;)Lcom/composegears/leviathan/ProvidableDependency;
2736
protected final fun valueOf (Ljava/lang/Object;)Lcom/composegears/leviathan/Dependency;
2837
}
2938

3039
public final class com/composegears/leviathan/Leviathan$Companion {
3140
}
3241

42+
public final class com/composegears/leviathan/ProvidableDependency : com/composegears/leviathan/Dependency {
43+
public fun injectedIn (Lcom/composegears/leviathan/DIScope;)Ljava/lang/Object;
44+
public final fun provides (Lkotlin/jvm/functions/Function0;)V
45+
}
46+
3347
public final class com/composegears/leviathan/ValueDependency : com/composegears/leviathan/Dependency {
34-
public fun <init> (Ljava/lang/Object;)V
3548
public fun injectedIn (Lcom/composegears/leviathan/DIScope;)Ljava/lang/Object;
49+
public final fun provides (Ljava/lang/Object;)V
3650
}
3751

leviathan/api/jvm/leviathan.api

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@ public final class com/composegears/leviathan/DependencyInitializationScope {
1616
public final fun inject (Lcom/composegears/leviathan/Dependency;)Ljava/lang/Object;
1717
}
1818

19+
public final class com/composegears/leviathan/FactoryDependency : com/composegears/leviathan/Dependency {
20+
public fun injectedIn (Lcom/composegears/leviathan/DIScope;)Ljava/lang/Object;
21+
}
22+
23+
public final class com/composegears/leviathan/InstanceDependency : com/composegears/leviathan/Dependency {
24+
public fun injectedIn (Lcom/composegears/leviathan/DIScope;)Ljava/lang/Object;
25+
}
26+
1927
public abstract class com/composegears/leviathan/Leviathan {
2028
public static final field Companion Lcom/composegears/leviathan/Leviathan$Companion;
2129
public fun <init> ()V
@@ -24,14 +32,20 @@ public abstract class com/composegears/leviathan/Leviathan {
2432
protected static final fun getValue (Lcom/composegears/leviathan/Dependency;Lcom/composegears/leviathan/Leviathan;Lkotlin/reflect/KProperty;)Lcom/composegears/leviathan/Dependency;
2533
protected final fun instanceOf (ZLkotlin/jvm/functions/Function1;)Lcom/composegears/leviathan/Dependency;
2634
public static synthetic fun instanceOf$default (Lcom/composegears/leviathan/Leviathan;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/composegears/leviathan/Dependency;
35+
protected final fun providableOf (Lkotlin/jvm/functions/Function0;)Lcom/composegears/leviathan/ProvidableDependency;
2736
protected final fun valueOf (Ljava/lang/Object;)Lcom/composegears/leviathan/Dependency;
2837
}
2938

3039
public final class com/composegears/leviathan/Leviathan$Companion {
3140
}
3241

42+
public final class com/composegears/leviathan/ProvidableDependency : com/composegears/leviathan/Dependency {
43+
public fun injectedIn (Lcom/composegears/leviathan/DIScope;)Ljava/lang/Object;
44+
public final fun provides (Lkotlin/jvm/functions/Function0;)V
45+
}
46+
3347
public final class com/composegears/leviathan/ValueDependency : com/composegears/leviathan/Dependency {
34-
public fun <init> (Ljava/lang/Object;)V
3548
public fun injectedIn (Lcom/composegears/leviathan/DIScope;)Ljava/lang/Object;
49+
public final fun provides (Ljava/lang/Object;)V
3650
}
3751

leviathan/api/leviathan.klib.api

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,28 @@ abstract class com.composegears.leviathan/Leviathan { // com.composegears.leviat
1515

1616
final fun <#A1: kotlin/Any?> factoryOf(kotlin/Boolean = ..., kotlin/Function1<com.composegears.leviathan/DependencyInitializationScope, #A1>): com.composegears.leviathan/Dependency<#A1> // com.composegears.leviathan/Leviathan.factoryOf|factoryOf(kotlin.Boolean;kotlin.Function1<com.composegears.leviathan.DependencyInitializationScope,0:0>){0§<kotlin.Any?>}[0]
1717
final fun <#A1: kotlin/Any?> instanceOf(kotlin/Boolean = ..., kotlin/Function1<com.composegears.leviathan/DependencyInitializationScope, #A1>): com.composegears.leviathan/Dependency<#A1> // com.composegears.leviathan/Leviathan.instanceOf|instanceOf(kotlin.Boolean;kotlin.Function1<com.composegears.leviathan.DependencyInitializationScope,0:0>){0§<kotlin.Any?>}[0]
18+
final fun <#A1: kotlin/Any?> providableOf(kotlin/Function0<#A1>): com.composegears.leviathan/ProvidableDependency<#A1> // com.composegears.leviathan/Leviathan.providableOf|providableOf(kotlin.Function0<0:0>){0§<kotlin.Any?>}[0]
1819
final fun <#A1: kotlin/Any?> valueOf(#A1): com.composegears.leviathan/Dependency<#A1> // com.composegears.leviathan/Leviathan.valueOf|valueOf(0:0){0§<kotlin.Any?>}[0]
1920

2021
final object Companion // com.composegears.leviathan/Leviathan.Companion|null[0]
2122
}
2223

23-
final class <#A: kotlin/Any?> com.composegears.leviathan/ValueDependency : com.composegears.leviathan/Dependency<#A> { // com.composegears.leviathan/ValueDependency|null[0]
24-
constructor <init>(#A) // com.composegears.leviathan/ValueDependency.<init>|<init>(1:0){}[0]
24+
final class <#A: kotlin/Any?> com.composegears.leviathan/FactoryDependency : com.composegears.leviathan/Dependency<#A> { // com.composegears.leviathan/FactoryDependency|null[0]
25+
final fun injectedIn(com.composegears.leviathan/DIScope): #A // com.composegears.leviathan/FactoryDependency.injectedIn|injectedIn(com.composegears.leviathan.DIScope){}[0]
26+
}
27+
28+
final class <#A: kotlin/Any?> com.composegears.leviathan/InstanceDependency : com.composegears.leviathan/Dependency<#A> { // com.composegears.leviathan/InstanceDependency|null[0]
29+
final fun injectedIn(com.composegears.leviathan/DIScope): #A // com.composegears.leviathan/InstanceDependency.injectedIn|injectedIn(com.composegears.leviathan.DIScope){}[0]
30+
}
2531

32+
final class <#A: kotlin/Any?> com.composegears.leviathan/ProvidableDependency : com.composegears.leviathan/Dependency<#A> { // com.composegears.leviathan/ProvidableDependency|null[0]
33+
final fun injectedIn(com.composegears.leviathan/DIScope): #A // com.composegears.leviathan/ProvidableDependency.injectedIn|injectedIn(com.composegears.leviathan.DIScope){}[0]
34+
final fun provides(kotlin/Function0<#A>) // com.composegears.leviathan/ProvidableDependency.provides|provides(kotlin.Function0<1:0>){}[0]
35+
}
36+
37+
final class <#A: kotlin/Any?> com.composegears.leviathan/ValueDependency : com.composegears.leviathan/Dependency<#A> { // com.composegears.leviathan/ValueDependency|null[0]
2638
final fun injectedIn(com.composegears.leviathan/DIScope): #A // com.composegears.leviathan/ValueDependency.injectedIn|injectedIn(com.composegears.leviathan.DIScope){}[0]
39+
final fun provides(#A) // com.composegears.leviathan/ValueDependency.provides|provides(1:0){}[0]
2740
}
2841

2942
final class com.composegears.leviathan/DependencyInitializationScope { // com.composegears.leviathan/DependencyInitializationScope|null[0]

leviathan/src/commonMain/kotlin/com/composegears/leviathan/Leviathan.kt

Lines changed: 62 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,45 @@ public interface Dependency<T> {
4343
}
4444

4545
/**
46-
* A dependency that always provides the same constant value.
46+
* A dependency that always provides the same value.
47+
* The value can be updated using the [provides] method.
4748
*/
48-
public class ValueDependency<T>(
49-
private val value: T
49+
public class ValueDependency<T> internal constructor(
50+
private var value: T
5051
) : Dependency<T> {
5152
override fun injectedIn(scope: DIScope): T = value
53+
54+
/**
55+
* Updates the value of the dependency.
56+
*/
57+
public fun provides(newValue: T) {
58+
value = newValue
59+
}
5260
}
5361

54-
internal class FactoryDependency<T>(
62+
/**
63+
* A dependency that provides a value using a provider function.
64+
* The provider function can be updated to supply a new value.
65+
*/
66+
public class ProvidableDependency<T> internal constructor(
67+
private var provider: () -> T
68+
) : Dependency<T> {
69+
override fun injectedIn(scope: DIScope): T = provider()
70+
71+
/**
72+
* Updates the provider function to supply a new value.
73+
*/
74+
public fun provides(valueProvider: () -> T) {
75+
provider = valueProvider
76+
}
77+
}
78+
79+
/**
80+
* A dependency that provides a new instance using a factory function.
81+
* If [useCache] is true, the same instance will be provided every time the dependency
82+
* is injected within the same [DIScope]. If false, a new instance will be created every time.
83+
*/
84+
public class FactoryDependency<T> internal constructor(
5585
private val useCache: Boolean,
5686
private val factory: DependencyInitializationScope.() -> T
5787
) : Dependency<T> {
@@ -69,7 +99,12 @@ internal class FactoryDependency<T>(
6999
} else factory(DependencyInitializationScope(scope))
70100
}
71101

72-
internal class InstanceDependency<T>(
102+
/**
103+
* A dependency that provides a singleton instance using a factory function.
104+
* If [keepAlive] is true, the instance will be kept alive after being injected at least once.
105+
* If false, the instance will be destroyed as soon as the last [DIScope] using it is closed.
106+
*/
107+
public class InstanceDependency<T> internal constructor(
73108
private val keepAlive: Boolean,
74109
private val factory: DependencyInitializationScope.() -> T
75110
) : Dependency<T> {
@@ -98,7 +133,7 @@ internal class InstanceDependency<T>(
98133

99134
/**
100135
* Base class for defining a module of dependencies.
101-
* Extend this class and use [valueOf], [factoryOf] and [instanceOf] to define dependencies.
136+
* Extend this class and use [valueOf], [providableOf], [factoryOf] and [instanceOf] to define dependencies.
102137
*
103138
* Example:
104139
* ```
@@ -113,6 +148,7 @@ internal class InstanceDependency<T>(
113148
* }
114149
* val interfaceRepo by instanceOf<SampleInterfaceRepo> { SampleInterfaceRepoImpl() }
115150
* val constantValue by valueOf(42)
151+
* val providable by providableOf { 34 }
116152
* }
117153
*
118154
* // ----- Usage -----
@@ -140,6 +176,8 @@ internal class InstanceDependency<T>(
140176
* fun foo() {
141177
* val scope = DIScope()
142178
* val repo1 = Module.autoCloseRepository.injectedIn(scope)
179+
* // update providable value
180+
* (Module.providable as? ProvidableDependency<Int>)?.provides { 21 }
143181
* ...
144182
* scope.close()
145183
* }
@@ -154,16 +192,28 @@ public abstract class Leviathan {
154192
): Dependency<T> = this
155193
}
156194

157-
/** Defines a dependency as a constant value.
158-
*
159-
* The same instance will be provided every time the dependency is injected.
195+
/**
196+
* Defines a dependency as a value.
197+
* The value can be updated using the [ValueDependency.provides] method.
198+
* The value will be provided every time the dependency is injected.
160199
*/
161200
protected fun <T> valueOf(
162201
value: T
163202
): Dependency<T> =
164203
ValueDependency(value)
165204

166-
/** Defines a dependency as a factory function.
205+
/**
206+
* Defines a dependency as a providable function.
207+
* The provider function can be updated using [ProvidableDependency.provides] method.
208+
* The instance from the provider will be provided every time the dependency is injected.
209+
*/
210+
protected fun <T> providableOf(
211+
provider: () -> T
212+
): ProvidableDependency<T> =
213+
ProvidableDependency(provider)
214+
215+
/**
216+
* Defines a dependency as a factory function.
167217
* If [useCache] is true (default), the same instance will be provided every time the dependency
168218
* is injected within the same [DIScope]. If false, a new instance will be created every time.
169219
*/
@@ -173,7 +223,8 @@ public abstract class Leviathan {
173223
): Dependency<T> =
174224
FactoryDependency(useCache, factory)
175225

176-
/** Defines a dependency as a singleton instance.
226+
/**
227+
* Defines a dependency as a singleton instance.
177228
* If [keepAlive] is true, the instance will be kept alive after being injected at least once.
178229
* If false (default), the instance will be destroyed as soon as the last [DIScope] using it is closed.
179230
*/

leviathan/src/commonTest/kotlin/com/composegears/leviathan/Test.kt

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ class ServiceLocator(externalServices: ExternalServices) : Leviathan() {
4141
// cyclic
4242
val cyclicDep1: Dependency<CyclicService> by instanceOf { CyclicService { inject(cyclicDep2) } }
4343
val cyclicDep2: Dependency<CyclicService> by instanceOf { CyclicService { inject(cyclicDep1) } }
44+
45+
// value
46+
val valueDep by valueOf(Service())
47+
48+
// providable
49+
val providableDep by providableOf { Service() }
4450
}
4551

4652
//------------Code------------
@@ -281,6 +287,46 @@ class Tests {
281287
assertNotEquals(external1, external2, "External dependencies should be independent")
282288
}
283289

290+
// ValueDependency tests
291+
@Test
292+
fun `valueDep - provides consistent value instance`() {
293+
val externalServices = ExternalServices()
294+
val serviceLocator = ServiceLocator(externalServices)
295+
val scope1 = DIScope()
296+
val scope2 = DIScope()
297+
val value1 = serviceLocator.valueDep.injectedIn(scope1)
298+
val value2 = serviceLocator.valueDep.injectedIn(scope1)
299+
val value3 = serviceLocator.valueDep.injectedIn(scope2)
300+
assertEquals(value1, value2, "Value dependency should provide consistent instance")
301+
assertEquals(value2, value3, "Value dependency should provide consistent instance")
302+
}
303+
304+
@Test
305+
fun `valueDep - reflects changes to the value`() {
306+
val externalServices = ExternalServices()
307+
val serviceLocator = ServiceLocator(externalServices)
308+
val scope = DIScope()
309+
val value1 = serviceLocator.valueDep.injectedIn(scope)
310+
val newValue = Service()
311+
(serviceLocator.valueDep as? ValueDependency<Service>)?.provides(newValue)
312+
val value2 = serviceLocator.valueDep.injectedIn(scope)
313+
assertNotEquals(newValue, value1, "Updated value should not match old instance")
314+
assertEquals(newValue, value2, "Updated value should match new instance")
315+
}
316+
317+
// ProvidableDependency tests
318+
319+
@Test
320+
fun `providableDep - reflect changes to provider`() {
321+
val externalServices = ExternalServices()
322+
val serviceLocator = ServiceLocator(externalServices)
323+
val scope = DIScope()
324+
val providedInstance = Service()
325+
(serviceLocator.providableDep as? ProvidableDependency<Service>)?.provides { providedInstance }
326+
val instance = serviceLocator.providableDep.injectedIn(scope)
327+
assertEquals(providedInstance, instance, "Providable dependency should return provided instance")
328+
}
329+
284330
// Scope behavior combination tests
285331
@Test
286332
fun `mixed dependencies - behave correctly in same scope`() {

0 commit comments

Comments
 (0)