Skip to content

Commit f9bfcd1

Browse files
vkatzegorikftp
andauthored
Leviathan v3 (#4)
v3 --------- Co-authored-by: Yahor <[email protected]>
1 parent a904e87 commit f9bfcd1

File tree

16 files changed

+621
-429
lines changed

16 files changed

+621
-429
lines changed

.github/workflows/verify.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ jobs:
3131
- uses: actions/checkout@v5
3232
- uses: gradle/actions/setup-gradle@v5
3333
- name: Check ABI
34-
run: ./gradlew checkLegacyAbi
34+
run: ./gradlew checkAbi
3535

3636
verify-libs:
3737
runs-on: ubuntu-latest

README.md

Lines changed: 38 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -39,21 +39,23 @@ Base usage
3939

4040
Create `Module` (recommend to use `object`) and extends from `Leviathan` class
4141

42-
Create fields using one functions:
42+
Create fields using these functions:
4343

44-
- Use `by instanceOf` to create single-object-delegate (same instance upon every access)
45-
- Use `by lateInitInstance` to create instance-based late-init dependency (ps: you need to call `provides` method before
46-
access)
47-
- Use `by factoryOf` to create factory-delegate (new instance upon each access)
44+
- Use `by instanceOf(keepAlive){/**/}` to create instance dependency
45+
- `keepAlive = true` : instance persists across different scopes
46+
- `keepAlive = false`(default): instance is auto-closed when all scopes close
47+
- Use `by factoryOf(useCache)` to create factory dependency
48+
- `useCache = true` (default): caches instances within the same scope
49+
- `useCache = false`: creates new instance on each access
50+
- Use `by valueOf(value)` to create value dependency (returns the same value always)
4851

49-
Both functions return a dependency provider instance and the type of field will be `Dependency<Type>`
52+
All functions return a `Dependency<Type>` instance.
5053

51-
To retreive dependency use either `Module.dependency.get()` or define a property `val dep by Module.dependency`
5254

53-
Simple case
55+
Example
5456
-----------
5557

56-
Declare you dependencies
58+
Declare your dependencies
5759

5860
```kotlin
5961
class SampleRepository()
@@ -68,156 +70,45 @@ Create module
6870

6971
```kotlin
7072
object Module : Leviathan() {
71-
val lazyRepository by instanceOf(::SampleRepository)
72-
val nonLazyRepository by instanceOf(false, ::SampleRepository)
73+
val autoCloseRepository by instanceOf { SampleRepository() }
74+
val keepAliveRepository by instanceOf(keepAlive = true) { SampleRepository() }
7375
val repositoryWithParam by factoryOf { SampleRepositoryWithParam(1) }
74-
val repositoryWithDependency by instanceOf { SampleRepositoryWithDependency(lazyRepository.get()) }
75-
val interfaceRepo by instanceOf<SampleInterfaceRepo>(::SampleInterfaceRepoImpl)
76+
val repositoryWithDependency by instanceOf {
77+
SampleRepositoryWithDependency(inject(autoCloseRepository))
78+
}
79+
val interfaceRepo by instanceOf<SampleInterfaceRepo> { SampleInterfaceRepoImpl() }
80+
val constantValue by valueOf(42)
7681
}
7782
```
7883

7984
Dependencies usage:
8085

8186
```kotlin
82-
fun foo() {
83-
val repo = Module.lazyRepository.get()
84-
//...
85-
}
86-
87-
class Model(
88-
val dep1: SampleRepository = Module.lazyRepository.get()
89-
) {
90-
val dep2: SampleRepository by Module.nonLazyRepository
91-
//...
92-
}
93-
94-
```
95-
96-
Mutli-module case
97-
-----------------
98-
99-
Interface based approach
100-
101-
```kotlin
102-
// ----------Module 1-------------
103-
//Dependency
104-
class Dep {
105-
fun foo() {}
106-
}
107-
108-
// ----------Module 2-------------
109-
// Dependency provider interface
110-
interface ICore {
111-
val dep: Dependency<Dep>
112-
}
113-
114-
// Dependency provider implementation
115-
internal class CoreImpl : Leviathan(), ICore {
116-
override val dep by instanceOf { Dep() }
117-
}
118-
// Dependency provider accessor
119-
val Core: ICore = CoreImpl()
120-
121-
// ----------Module 3-------------
122-
// Usage
123-
fun boo() {
124-
val dep by Core.dep
125-
}
126-
```
127-
128-
Simple approach
129-
130-
```kotlin
131-
// ----------Module 1-------------
132-
//Dependency
133-
class Dep {
134-
fun foo() {}
135-
}
136-
137-
// ----------Module 2-------------
138-
// Dependency provider & accessor
139-
object Core : Leviathan() {
140-
val dep by instanceOf { Dep() }
141-
}
142-
143-
// ----------Module 3-------------
144-
// Usage
145-
fun boo() {
146-
val dep by Core.dep
147-
}
148-
```
149-
150-
Advanced case
151-
-------------
152-
153-
In order to create good & testable classes recommend to use advanced scenario
154-
155-
1) declare dependencies
156-
```kotlin
157-
class DataRepository //...
158-
class ApiRepository //...
159-
```
160-
2) declare module interface (data/domain modules)
161-
```kotlin
162-
interface DataModule {
163-
val dataRepository: Dependency<DataRepository>
164-
}
165-
166-
interface ApiModule {
167-
val apiRepository: Dependency<ApiRepository>
168-
}
169-
```
170-
3) Create `AppModule` and inherit from interfaces(step #2) and `Leviathan`
171-
```kotlin
172-
object AppModule : DataModule, ApiModule, Leviathan() {
173-
override val dataRepository: Dependency<DataRepository> by instance(::DataRepository)
174-
override val apiRepository: Dependency<ApiRepository> by instance(::ApiRepository)
87+
// view model
88+
class SomeVM(
89+
dep1: Dependency<SampleRepository> = Module.autoCloseRepository,
90+
) : ViewModel() {
91+
val dep1value = inject(dep1)
92+
93+
fun foo(){
94+
val dep2 = inject(Module.interfaceRepo)
17595
}
176-
```
177-
4) Create Models (or any other classes) base on interfaces from step #2
178-
```kotlin
179-
class Model(apiModule: ApiModule = AppModule){
180-
val api: ApiRepository by apiModule.apiRepository
181-
182-
fun foo(){/*...*/}
183-
}
184-
```
185-
186-
Now you can make tests and have easy way to mock your data:
187-
188-
```kotlin
189-
@Test
190-
fun ModelTests() {
191-
val model = Model(object : Leviathan(), ApiModule {
192-
override val apiRepository by instanceOf { MyMockApiRepository() }
193-
})
194-
model.foo()
195-
196-
//-----or-----------
197-
198-
AppModule.apiRepository.overrideWith { MyMockApiRepository() }
199-
val model = Model()
200-
model.foo()
201-
}
202-
```
203-
204-
Compose
205-
-------------
206-
207-
Dependencies access in compose code:
208-
```kotlin
209-
class Repository(){
210-
fun foo(){}
21196
}
21297

213-
object Module : Leviathan(){
214-
val dependency by instanceOf { Repository() }
98+
// compose
99+
@Composable
100+
fun ComposeWithDI() {
101+
val repo1 = inject(Module.autoCloseRepository)
102+
val repo2 = inject { Module.repositoryWithParam }
103+
/*..*/
215104
}
216105

217-
@Composable
218-
fun SomeComposable(){
219-
val dependency = leviathanInject { Module.dependency }
220-
///...
106+
// random access
107+
fun foo() {
108+
val scope = DIScope()
109+
val repo1 = Module.autoCloseRepository.injectedIn(scope)
110+
/*..*/
111+
scope.close()
221112
}
222113
```
223114

build.gradle.kts

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import io.gitlab.arturbosch.detekt.DetektPlugin
2-
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
32

43
plugins {
5-
alias(libs.plugins.android.library) apply false
6-
alias(libs.plugins.compose.compiler)
4+
alias(libs.plugins.android.kotlin.multiplatform.library) apply false
5+
alias(libs.plugins.compose.compiler) apply false
76
alias(libs.plugins.jetbrains.compose) apply false
87
alias(libs.plugins.detekt)
98
alias(libs.plugins.kotlin.multiplatform) apply false
@@ -42,19 +41,16 @@ allprojects {
4241
}
4342
}
4443

45-
subprojects {
46-
tasks.withType<KotlinCompile>().configureEach {
47-
val outPath = layout.buildDirectory.dir("compose_compiler").get().asFile.absoluteFile
48-
49-
compilerOptions {
50-
if (project.findProperty("composeCompilerReports") == "true") {
51-
composeCompiler {
52-
reportsDestination = outPath
53-
metricsDestination = outPath
54-
}
55-
}
56-
}
57-
}
44+
// check ABI
45+
tasks.register("checkAbi") {
46+
dependsOn(":leviathan:checkLegacyAbi")
47+
dependsOn(":leviathan-compose:checkLegacyAbi")
48+
}
49+
50+
// update ABI
51+
tasks.register("updateAbi") {
52+
dependsOn(":leviathan:updateLegacyAbi")
53+
dependsOn(":leviathan-compose:updateLegacyAbi")
5854
}
5955

6056
createM2PTask()

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 = "2.0.0"
2+
leviathan = "3.0.0"

gradle/libs.versions.toml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[versions]
22
agp = "8.12.0"
3-
android-compileSdk = "35"
4-
android-minSdk = "21"
3+
minSdk = "21"
4+
compileSdk = "36"
55

66
detekt = "1.23.8"
77
jetbrains-compose = "1.9.0"
@@ -12,9 +12,10 @@ detekt-compose = "io.nlopez.compose.rules:detekt:0.4.27"
1212
detekt-formatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" }
1313

1414
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
15+
lifecycle-viewmodel-compose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version = "2.9.4" }
1516

1617
[plugins]
17-
android-library = { id = "com.android.library", version.ref = "agp" }
18+
android-kotlin-multiplatform-library = { id = "com.android.kotlin.multiplatform.library", version.ref = "agp" }
1819
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
1920
detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }
2021
jetbrains-compose = { id = "org.jetbrains.compose", version.ref = "jetbrains-compose" }
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
public final class com/composegears/leviathan/compose/LeviathanComposeKt {
2-
public static final fun leviathanInject (Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object;
2+
public static final fun inject (Landroidx/lifecycle/ViewModel;Lcom/composegears/leviathan/Dependency;)Ljava/lang/Object;
3+
public static final fun inject (Lcom/composegears/leviathan/Dependency;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object;
4+
public static final fun inject (Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object;
35
}
46

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
public final class com/composegears/leviathan/compose/LeviathanComposeKt {
2-
public static final fun leviathanInject (Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object;
2+
public static final fun inject (Landroidx/lifecycle/ViewModel;Lcom/composegears/leviathan/Dependency;)Ljava/lang/Object;
3+
public static final fun inject (Lcom/composegears/leviathan/Dependency;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object;
4+
public static final fun inject (Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object;
35
}
46

leviathan-compose/api/leviathan-compose.klib.api

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,6 @@
66
// - Show declarations: true
77

88
// Library unique name: <io.github.composegears:leviathan-compose>
9-
final fun <#A: kotlin/Any?> com.composegears.leviathan.compose/leviathanInject(kotlin/Function0<com.composegears.leviathan/Dependency<#A>>, androidx.compose.runtime/Composer?, kotlin/Int): #A // com.composegears.leviathan.compose/leviathanInject|leviathanInject(kotlin.Function0<com.composegears.leviathan.Dependency<0:0>>;androidx.compose.runtime.Composer?;kotlin.Int){0§<kotlin.Any?>}[0]
9+
final fun <#A: kotlin/Any?> (androidx.lifecycle/ViewModel).com.composegears.leviathan.compose/inject(com.composegears.leviathan/Dependency<#A>): #A // com.composegears.leviathan.compose/inject|[email protected](com.composegears.leviathan.Dependency<0:0>){0§<kotlin.Any?>}[0]
10+
final fun <#A: kotlin/Any?> com.composegears.leviathan.compose/inject(com.composegears.leviathan/Dependency<#A>, androidx.compose.runtime/Composer?, kotlin/Int): #A // com.composegears.leviathan.compose/inject|inject(com.composegears.leviathan.Dependency<0:0>;androidx.compose.runtime.Composer?;kotlin.Int){0§<kotlin.Any?>}[0]
11+
final fun <#A: kotlin/Any?> com.composegears.leviathan.compose/inject(kotlin/Function0<com.composegears.leviathan/Dependency<#A>>, androidx.compose.runtime/Composer?, kotlin/Int): #A // com.composegears.leviathan.compose/inject|inject(kotlin.Function0<com.composegears.leviathan.Dependency<0:0>>;androidx.compose.runtime.Composer?;kotlin.Int){0§<kotlin.Any?>}[0]

leviathan-compose/build.gradle.kts

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
1+
import com.android.build.api.dsl.androidLibrary
22
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
33
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
44
import org.jetbrains.kotlin.gradle.dsl.abi.ExperimentalAbiValidation
55

66
plugins {
77
alias(libs.plugins.kotlin.multiplatform)
8-
alias(libs.plugins.android.library)
8+
alias(libs.plugins.android.kotlin.multiplatform.library)
99
alias(libs.plugins.compose.compiler)
1010
alias(libs.plugins.jetbrains.compose)
1111
alias(libs.plugins.m2p)
@@ -22,11 +22,15 @@ kotlin {
2222
}
2323

2424
jvm()
25-
androidTarget {
26-
publishLibraryVariants("release")
27-
@OptIn(ExperimentalKotlinGradlePluginApi::class)
28-
compilerOptions {
29-
jvmTarget = JvmTarget.JVM_1_8
25+
androidLibrary {
26+
namespace = "com.composegears.leviathan.compose"
27+
compileSdk = libs.versions.compileSdk.get().toInt()
28+
minSdk = libs.versions.minSdk.get().toInt()
29+
30+
compilations.configureEach {
31+
compilerOptions.configure {
32+
jvmTarget = JvmTarget.JVM_1_8
33+
}
3034
}
3135
}
3236
iosX64()
@@ -35,28 +39,20 @@ kotlin {
3539

3640
@OptIn(ExperimentalWasmDsl::class)
3741
wasmJs {
38-
binaries.executable()
39-
nodejs()
42+
browser()
4043
}
4144

4245
sourceSets {
4346
commonMain.dependencies {
44-
implementation(projects.leviathan)
47+
api(projects.leviathan)
4548

4649
implementation(compose.foundation)
4750
implementation(compose.runtime)
51+
implementation(libs.lifecycle.viewmodel.compose)
4852
}
4953
}
5054
}
5155

52-
android {
53-
namespace = "io.github.composegears.leviathan.compose"
54-
compileSdk = libs.versions.android.compileSdk.get().toInt()
55-
defaultConfig {
56-
minSdk = libs.versions.android.minSdk.get().toInt()
57-
}
58-
}
59-
6056
m2p {
6157
description = "Leviathan Compose integration"
6258
}

0 commit comments

Comments
 (0)