Skip to content

Conversation

@vkatz
Copy link
Contributor

@vkatz vkatz commented Oct 2, 2025

Resolve #3 points

Summary by CodeRabbit

  • New Features

    • Introduced scoped DI with DIScope and lifecycle-aware dependencies.
    • Added valueOf, factoryOf(useCache), instanceOf(keepAlive) APIs.
    • New inject() for Compose and ViewModel-based injection.
  • Refactor

    • Replaced leviathanInject with inject and added multiple overloads.
    • Simplified and unified DI DSL and repository patterns.
  • Documentation

    • README updated with new DSL, examples, and scope-based usage.
  • Chores

    • Upgraded Gradle wrapper to 9.1 and moved to Android Kotlin Multiplatform plugin.
    • Bumped toolchain and library versions; added lifecycle-viewmodel-compose.
    • Added checkAbi and updateAbi tasks; enabled ABI validation.

@vkatz vkatz requested a review from egorikftp October 2, 2025 21:38
@vkatz
Copy link
Contributor Author

vkatz commented Oct 2, 2025

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Oct 2, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link

coderabbitai bot commented Oct 2, 2025

Important

Review skipped

Auto reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Refactors the DI core to a scoped Dependency API with valueOf/factoryOf/instanceOf, adds Compose and ViewModel injection helpers, overhauls examples/docs, updates tests to new DSL, and migrates build to Android Kotlin Multiplatform with version bumps and new ABI tasks/wrapper upgrades. Public APIs change accordingly.

Changes

Cohort / File(s) Summary
Documentation and examples
README.md
Updates docs to new DI DSL: instanceOf(keepAlive), factoryOf(useCache), valueOf; new module examples; Compose/ViewModel usage; reorganized sections.
Build: root and wrappers
build.gradle.kts, gradle/wrapper/gradle-wrapper.properties, gradlew, gradlew.bat
Switch to android.kotlin.multiplatform.library plugin; remove kotlin.parcelize; add subproject tasks checkAbi/updateAbi; remove apiValidation block; upgrade Gradle to 9.1; wrapper scripts use -jar invocation.
Version catalogs
gradle/libs.versions.toml
Bump AGP/Kotlin/Compose/Detekt; add lifecycle-viewmodel-compose; replace android-application/android-library with android-kotlin-multiplatform-library; clean plugin entries.
Compose module: API dumps
leviathan-compose/api/android/leviathan-compose.api, leviathan-compose/api/jvm/leviathan-compose.api, leviathan-compose/api/leviathan-compose.klib.api
Remove leviathanInject; add three inject overloads (ViewModel, Dependency+Composer, Function0+Composer).
Compose module: build
leviathan-compose/build.gradle.kts
Switch to Android KMP library; version 3.0.0; expose projects.leviathan as api; add lifecycle-viewmodel-compose; wasmJs uses browser(); configure androidLibrary namespace/SDKs/JVM target.
Compose module: implementation
leviathan-compose/src/commonMain/kotlin/.../LeviathanCompose.kt
Add ViewModelDIScope and ViewModel.inject; add @composable inject(Dependency) with remember/DisposableEffect; replace leviathanInject with inject variants.
Core module: build
leviathan/build.gradle.kts
Switch to Android KMP library; version 3.0.0; add abiValidation and ExperimentalAbiValidation; wasmJs uses browser(); configure androidLibrary namespace/SDKs/JVM target.
Core module: DI implementation
leviathan/src/commonMain/kotlin/.../Leviathan.kt
Introduces DIScope (with GLOBAL, onClose), DependencyInitializationScope, and unified Dependency API: ValueDependency, FactoryDependency (optional cache), InstanceDependency (keepAlive). Adds Leviathan helpers: valueOf, factoryOf, instanceOf, and @JvmStatic delegation operator. Removes previous provider types.
Tests updated to new DSL
leviathan/src/commonTest/kotlin/.../Test.kt
Replace old members with autoClose/keepAlive instances, cached/always-new factories, dependency wiring via inject, cyclic deps via inject closures, and external delegation.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as Composable caller
  participant C as Compose runtime
  participant S as DIScope (composition)
  participant D as Dependency<T>

  U->>C: inject(dependency)
  rect rgba(200,220,255,0.25)
    note right of C: Remember per-composition scope
    C->>C: remember { DIScope() }
    C->>D: injectedIn(S)
    D-->>C: T
    C-->>U: T
  end
  rect rgba(255,230,200,0.25)
    note right of C: Dispose composition
    C->>S: onDispose -> close()
  end
Loading
sequenceDiagram
  autonumber
  actor VM as ViewModel
  participant VS as ViewModelDIScope
  participant IS as Internal DIScope
  participant D as Dependency<T>

  VM->>VS: inject(dependency)
  alt first call
    VS->>VS: create IS if absent
  end
  VS->>D: injectedIn(IS)
  D-->>VM: T
  note over VM,VS: On ViewModel.onCleared()
  VM->>VS: close()
  VS->>IS: close()
Loading

Pre-merge checks and finishing touches

❌ Failed checks (3 warnings)
Check name Status Explanation Resolution
Title Check ⚠️ Warning The title “Leviathan v3” is merely a version label and does not describe the main changes such as the new scoped DI core, DIScope API, or Compose and ViewModel injection integrations introduced in this PR. Revise the title to briefly summarize the primary enhancement, for example “Introduce scoped DI core with DIScope, ViewModel and Compose inject APIs.”
Out of Scope Changes Check ⚠️ Warning The PR contains extensive build system and versioning updates—gradle plugin and wrapper changes, version catalog adjustments, new CLI tasks—that are not related to implementing the scope controller features defined in issue #3. Separate the build and version bump modifications into their own PR and keep this one focused on the DI scope controller and related integrations.
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. You can run `@coderabbitai generate docstrings` to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit's high-level summary is enabled.
Linked Issues Check ✅ Passed All objectives from issue #3 have been addressed: clearable instances and DIScope with onClose hooks are implemented, custom scopes are supported via injectedIn(scope), ViewModel integration is provided by ViewModel.inject, and Compose integration via the new composable inject API.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (3)
leviathan-compose/src/commonMain/kotlin/com/composegears/leviathan/compose/LeviathanCompose.kt (1)

35-44: Consider adding dependency as a key to remember.

Currently, the remember block that calls dependency.injectedIn(scope) doesn't include dependency as a key. If the dependency parameter changes between recompositions, the remembered value won't update.

Apply this diff if dependency is expected to change:

-    return remember { dependency.injectedIn(scope) }
+    return remember(dependency) { dependency.injectedIn(scope) }

However, if dependency is guaranteed to be stable (e.g., coming from a Module object), the current implementation is fine.

leviathan/src/commonMain/kotlin/com/composegears/leviathan/Leviathan.kt (2)

8-25: Consider thread safety for DIScope.

The closeActions list uses mutableListOf, which is not thread-safe. If onClose() or close() can be called from multiple threads concurrently, this could lead to race conditions.

If multi-threaded access is expected, consider using a thread-safe collection:

-    private val closeActions = mutableListOf<() -> Unit>()
+    private val closeActions = mutableListOf<() -> Unit>()
+    private val lock = Any()
     
     internal open fun onClose(action: () -> Unit) {
+        synchronized(lock) {
             closeActions += action
+        }
     }
     
     public fun close() {
+        val actions = synchronized(lock) {
+            closeActions.toList().also { closeActions.clear() }
+        }
-        closeActions.onEach { it() }
-        closeActions.clear()
+        actions.forEach { it() }
     }

However, if DIScope is intended for single-threaded use (e.g., within a ViewModel or Composition), the current implementation is acceptable.


46-62: Consider thread safety for FactoryDependency cache.

The scopedCache map is not thread-safe. If injectedIn() is called concurrently for the same scope, it could create duplicate instances or cause data corruption.

Consider using synchronized or a concurrent map:

-    private val scopedCache = mutableMapOf<DIScope, T>()
+    private val scopedCache = mutableMapOf<DIScope, T>()
+    private val lock = Any()
+    
     override fun injectedIn(scope: DIScope): T =
         if (useCache) {
-            scopedCache.getOrElse(scope) {
-                val instance = factory(DependencyInitializationScope(scope))
-                scopedCache[scope] = instance
-                scope.onClose {
-                    scopedCache.remove(scope)
-                }
-                instance
+            synchronized(lock) {
+                scopedCache.getOrPut(scope) {
+                    val instance = factory(DependencyInitializationScope(scope))
+                    scope.onClose {
+                        synchronized(lock) {
+                            scopedCache.remove(scope)
+                        }
+                    }
+                    instance
+                }
             }
         } else factory(DependencyInitializationScope(scope))
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7867702 and e582230.

📒 Files selected for processing (14)
  • README.md (2 hunks)
  • build.gradle.kts (2 hunks)
  • gradle/libs.versions.toml (1 hunks)
  • gradle/wrapper/gradle-wrapper.properties (1 hunks)
  • gradlew (4 hunks)
  • gradlew.bat (2 hunks)
  • leviathan-compose/api/android/leviathan-compose.api (1 hunks)
  • leviathan-compose/api/jvm/leviathan-compose.api (1 hunks)
  • leviathan-compose/api/leviathan-compose.klib.api (1 hunks)
  • leviathan-compose/build.gradle.kts (2 hunks)
  • leviathan-compose/src/commonMain/kotlin/com/composegears/leviathan/compose/LeviathanCompose.kt (1 hunks)
  • leviathan/build.gradle.kts (2 hunks)
  • leviathan/src/commonMain/kotlin/com/composegears/leviathan/Leviathan.kt (1 hunks)
  • leviathan/src/commonTest/kotlin/com/composegears/leviathan/Test.kt (1 hunks)
🧰 Additional context used
🪛 markdownlint-cli2 (0.18.1)
README.md

55-55: Heading style
Expected: atx; Actual: setext

(MD003, heading-style)

🔇 Additional comments (22)
gradlew.bat (1)

76-76: LGTM! Wrapper invocation updated to direct jar execution.

The change from -classpath with GradleWrapperMain to -jar gradle-wrapper.jar aligns with Gradle's standard wrapper pattern and simplifies the invocation.

gradlew (1)

89-89: LGTM! Wrapper script improvements and direct jar invocation.

The changes include:

  • Safer path handling with printf '%s\n' "$PWD" to avoid special character issues.
  • Direct jar invocation (-jar gradle-wrapper.jar) consistent with the Windows script and Gradle's standard pattern.
  • Security improvement: exec replaces eval for final command execution, eliminating shell expansion risks.

Also applies to: 213-213, 248-248

leviathan-compose/api/leviathan-compose.klib.api (1)

9-11: LGTM! New inject API surface added.

The three inject overloads provide ViewModel-scoped DI, composable injection, and lambda-based injection. The declarations are consistent with the PR objectives and align with the new DI surface.

leviathan-compose/api/android/leviathan-compose.api (1)

2-4: LGTM! New inject API surface consistent with klib dump.

The three inject overloads replace leviathanInject and provide ViewModel-scoped DI, composable injection, and lambda-based injection. The declarations are consistent with the klib.api and align with the PR objectives.

leviathan-compose/build.gradle.kts (3)

19-22: LGTM: ABI validation enabled.

Enabling ABI validation is essential for maintaining binary compatibility across library versions.


51-51: Correct use of api() instead of implementation().

Since this module exposes Leviathan types in its public API (e.g., Dependency<T> in function signatures), using api(projects.leviathan) ensures proper transitive dependency exposure.


55-55: LGTM: lifecycle-viewmodel-compose dependency added.

This dependency correctly supports the new ViewModel.inject() extension function introduced in this PR.

leviathan-compose/src/commonMain/kotlin/com/composegears/leviathan/compose/LeviathanCompose.kt (2)

12-22: LGTM: ViewModelDIScope implementation.

The ViewModelDIScope wrapper correctly integrates with ViewModel's AutoCloseable mechanism, ensuring the DIScope is cleaned up when the ViewModel is cleared.


24-31: LGTM: ViewModel.inject extension.

The implementation correctly:

  • Lazily creates and stores a ViewModelDIScope using the ViewModel's closeable registry
  • Reuses the scope across multiple inject calls within the same ViewModel
  • Ensures cleanup via the ViewModel lifecycle
leviathan/build.gradle.kts (2)

16-23: LGTM: ABI validation and compiler options.

The ABI validation is correctly enabled, and the opt-in to ExperimentalComposeUiApi is appropriate for a library that may expose Compose types.


26-36: LGTM: androidLibrary configuration.

The migration from androidTarget to androidLibrary is correctly configured with appropriate namespace, SDK versions, and JVM target.

leviathan-compose/api/jvm/leviathan-compose.api (1)

1-5: LGTM: API surface updated.

The API definition correctly reflects the new inject overloads introduced in the implementation, replacing the previous leviathanInject entry point.

README.md (3)

42-53: LGTM: Clear documentation of new DSL.

The documentation clearly explains the new dependency declaration functions (instanceOf, factoryOf, valueOf) with their parameters and behavior. This will help users understand the new API.


72-82: LGTM: Comprehensive module examples.

The module examples demonstrate all key patterns:

  • Auto-close and keep-alive instances
  • Factory with parameters
  • Dependency injection via inject()
  • Interface-based dependencies
  • Constant values

87-113: LGTM: Usage examples cover all integration points.

The examples correctly demonstrate:

  • ViewModel integration with default parameter injection
  • Compose integration with inject()
  • Manual scope management for random access
leviathan/src/commonTest/kotlin/com/composegears/leviathan/Test.kt (6)

52-88: LGTM: Comprehensive DIScope lifecycle tests.

The tests correctly verify:

  • Close actions are executed
  • Multiple close actions execute in order
  • Close actions only execute once
  • GLOBAL scope doesn't execute close actions

90-130: LGTM: Factory behavior tests.

The tests thoroughly verify the two factory modes:

  • cachedFactory (useCache=true) caches within scope, creates new across scopes
  • alwaysNewFactory (useCache=false) always creates new instances

132-206: LGTM: Instance lifecycle tests.

The tests comprehensively verify:

  • autoCloseInstance (keepAlive=false) reuses within scope and nullifies when all scopes close
  • keepAliveInstance (keepAlive=true) persists across scopes and survives closure

208-238: LGTM: Dependency injection tests.

The tests verify that inject() correctly resolves dependencies during initialization, including nested dependency chains.


240-282: LGTM: Cyclic and external dependency tests.

The tests verify:

  • Cyclic dependencies resolve correctly via lazy lambdas
  • External dependencies from other modules work correctly and maintain independence

284-323: LGTM: Mixed dependency behavior tests.

The tests verify that different dependency types behave correctly when used together within the same scope and across different scopes.

leviathan/src/commonMain/kotlin/com/composegears/leviathan/Leviathan.kt (1)

91-116: LGTM: Leviathan DSL implementation.

The valueOf, factoryOf, and instanceOf helper functions provide a clean, declarative API for defining dependencies. The use of @JvmStatic on the getValue operator correctly enables property delegation on the JVM.

adjust tests so it now compilable for iOS
@vkatz vkatz merged commit f9bfcd1 into main Oct 5, 2025
6 checks passed
@vkatz vkatz deleted the v3 branch October 5, 2025 19:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Create scope controller

3 participants