Skip to content

Commit

Permalink
Leviathan 2 (#2)
Browse files Browse the repository at this point in the history
Leviathan 2.0

More strict version of library, lower the amount of unintended options. Add more flexible testing override option. Add late-init dependency. Compose integration.
  • Loading branch information
vkatz authored Oct 28, 2024
1 parent 72a5eff commit 7867702
Show file tree
Hide file tree
Showing 28 changed files with 2,004 additions and 386 deletions.
13 changes: 13 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[*]
charset = utf-8
end_of_line = crlf
indent_size = 4
indent_style = space
insert_final_newline = false
max_line_length = 120
tab_width = 4

[{*.kt,*.kts}]
ij_kotlin_line_break_after_multiline_when_entry = false
ij_kotlin_name_count_to_use_star_import = 5
ij_kotlin_name_count_to_use_star_import_for_members = 3
36 changes: 18 additions & 18 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
name: Generate m2 packages
on:
workflow_dispatch:
workflow_dispatch:
jobs:
build:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 17
- name: Setup Gradle
uses: gradle/gradle-build-action@v3
- name: Execute Gradle build
run: ./gradlew :leviathan:publish -PPGP_KEY="${{secrets.PGP_KEY}}" -PPGP_PAS="${{secrets.PGP_PAS}}"
- uses: actions/upload-artifact@v4
with:
name: m2
path: library/build/m2
if-no-files-found: error
build:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 17
- name: Setup Gradle
uses: gradle/gradle-build-action@v3
- name: Execute Gradle build
run: ./gradlew createLocalM2 -PPGP_KEY="${{secrets.PGP_KEY}}" -PPGP_PAS="${{secrets.PGP_PAS}}"
- uses: actions/upload-artifact@v4
with:
name: m2
path: build/m2
if-no-files-found: error
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ local.properties
captures
.externalNativeBuild
.cxx
.kotlin
publish.bat
188 changes: 124 additions & 64 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,38 +10,50 @@


Add the dependency below to your **module**'s `build.gradle.kts` file:
#### Android / jvm
```kotlin
dependencies {
implementation("io.github.composegears:leviathan:$version")
}
```

| Module | Version |
|-------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|
| leviathan | [![Maven Central](https://img.shields.io/maven-central/v/io.github.composegears/leviathan.svg?style=flat-square)](https://central.sonatype.com/artifact/io.github.composegears/leviathan) |
| leviathan-compose | [![Maven Central](https://img.shields.io/maven-central/v/io.github.composegears/leviathan-compose.svg?style=flat-square)](https://central.sonatype.com/artifact/io.github.composegears/leviathan-compose) |

#### Multiplatform

```kotlin
sourceSets {
commonMain.dependencies {
// core library
implementation("io.github.composegears:leviathan:$version")
// Compose integration
implementation("io.github.composegears:leviathan-compose:$version")
}
}
```

#### Android / jvm

Use same dependencies in the `dependencies { ... }` section


Base usage
----------

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

Create fields using one of 2 functions:
Create fields using one functions:

- Use `by instance` to create single-object-delegate (same instance upon every access)
- Use `by factory` to create factory-delegate (new instance upon each access)
- Use `by instanceOf` to create single-object-delegate (same instance upon every access)
- Use `by lateInitInstance` to create instance-based late-init dependency (ps: you need to call `provides` method before
access)
- Use `by factoryOf` to create factory-delegate (new instance upon each access)

Use `by (instance|factory)<Interface>{InterfaceImpl}` to hide impl class
Both functions return a dependency provider instance and the type of field will be `Dependency<Type>`

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

Simple case
-----------

Declare you repositories
Declare you dependencies

```kotlin
class SampleRepository()
Expand All @@ -55,31 +67,27 @@ class SampleInterfaceRepoImpl : SampleInterfaceRepo
Create module

```kotlin
class Module : Leviathan() {
val lazyRepository by instance(::SampleRepository)
val nonLazyRepository by instance(false, ::SampleRepository)
val repositoryWithParam by factory { SampleRepositoryWithParam(1) }
val repositoryWithDependency by instance { SampleRepositoryWithDependency(lazyRepository) }
val interfaceRepo by instance<SampleInterfaceRepo>(::SampleInterfaceRepoImpl)
object Module : Leviathan() {
val lazyRepository by instanceOf(::SampleRepository)
val nonLazyRepository by instanceOf(false, ::SampleRepository)
val repositoryWithParam by factoryOf { SampleRepositoryWithParam(1) }
val repositoryWithDependency by instanceOf { SampleRepositoryWithDependency(lazyRepository.get()) }
val interfaceRepo by instanceOf<SampleInterfaceRepo>(::SampleInterfaceRepoImpl)
}
```

Dependencies usage:

```kotlin
fun foo() {
val repo = Module.lazyRepository
val repo = Module.lazyRepository.get()
//...
}

class Model(
val repo: SampleRepository = Module.lazyRepository
val dep1: SampleRepository = Module.lazyRepository.get()
) {
//...
}

class Model() {
private val repo = Module.lazyRepository
val dep2: SampleRepository by Module.nonLazyRepository
//...
}

Expand All @@ -88,38 +96,57 @@ class Model() {
Mutli-module case
-----------------

- HttpClient
- WeatherRepository <- HttpClient
- NewsRepository <- HttpClient
- App <- WeatherRepository, NewsRepository
Interface based approach

1) Http Client Module
```kotlin
class HttpClient {
// ...
}
```
2) Weather service module
```kotlin
class WeatherRepository(client: HttpClient) {
// ...
}
```
3) News service module
```kotlin
class NewsRepository(client: HttpClient) {
// ...
}
```
4) App service module
```kotlin
object AppModule : Leviathan() {
private val httpClient by instance { HttpClient() }
val weatherRepository by instance { WeatherRepository(httpClient) }
val newsRepository by instance { NewRepository(httpClient) }
}
```kotlin
// ----------Module 1-------------
//Dependency
class Dep {
fun foo() {}
}

// ----------Module 2-------------
// Dependency provider interface
interface ICore {
val dep: Dependency<Dep>
}

// Dependency provider implementation
internal class CoreImpl : Leviathan(), ICore {
override val dep by instanceOf { Dep() }
}
// Dependency provider accessor
val Core: ICore = CoreImpl()

// ----------Module 3-------------
// Usage
fun boo() {
val dep by Core.dep
}
```

Simple approach

```kotlin
// ----------Module 1-------------
//Dependency
class Dep {
fun foo() {}
}

// ----------Module 2-------------
// Dependency provider & accessor
object Core : Leviathan() {
val dep by instanceOf { Dep() }
}

// ----------Module 3-------------
// Usage
fun boo() {
val dep by Core.dep
}
```

Advanced case
-------------

Expand All @@ -133,45 +160,78 @@ In order to create good & testable classes recommend to use advanced scenario
2) declare module interface (data/domain modules)
```kotlin
interface DataModule {
val dataRepository: DataRepository
val dataRepository: Dependency<DataRepository>
}

interface ApiModule {
val apiRepository: ApiRepository
val apiRepository: Dependency<ApiRepository>
}
```
3) Create `AppModule` and inherit from interfaces(step #2) and `Leviathan`
```kotlin
object AppModule : DataModule, ApiModule, Leviathan() {
override val dataRepository: DataRepository by instance(::DataRepository)
override val apiRepository: ApiRepository by instance(::ApiRepository)
override val dataRepository: Dependency<DataRepository> by instance(::DataRepository)
override val apiRepository: Dependency<ApiRepository> by instance(::ApiRepository)
}
```
4) Create Models (or any other classes) base on interfaces from step #2
```kotlin
class Model(apiModule: ApiModule = AppModule){
val api = apiModule.apiRepository
val api: ApiRepository by apiModule.apiRepository

fun foo(){/*...*/}
}
```

Now you can make tests and have easy way to mock your data:

```kotlin
@Test
fun ModelTests(){
val model = Model(object : ApiModule {
override val apiRepository: ApiRepository
get() = ApiRepository() // mock
fun ModelTests() {
val model = Model(object : Leviathan(), ApiModule {
override val apiRepository by instanceOf { MyMockApiRepository() }
})
model.foo()

//-----or-----------

AppModule.apiRepository.overrideWith { MyMockApiRepository() }
val model = Model()
model.foo()
}
```

Compose
-------------

Dependencies access in compose code:
```kotlin
class Repository(){
fun foo(){}
}

object Module : Leviathan(){
val dependency by instanceOf { Repository() }
}

@Composable
fun SomeComposable(){
val dependency = leviathanInject { Module.dependency }
///...
}
```

## Contributors

Thank you for your help! ❤️

<a href="https://github.com/ComposeGears/Leviathan/graphs/contributors">
<img src="https://contrib.rocks/image?repo=ComposeGears/Leviathan" />
</a>


# License
```xml
```
Developed by ComposeGears 2024
Licensed under the Apache License, Version 2.0 (the "License");
Expand Down
Loading

0 comments on commit 7867702

Please sign in to comment.