From 58f6eb4c0b3ac679688e79acbf57027cf2298a23 Mon Sep 17 00:00:00 2001 From: "harry.tumalewa" Date: Mon, 27 Apr 2026 00:58:31 +0700 Subject: [PATCH] release: v1.0.0 --- CHANGELOG.md | 10 +- README.md | 42 ++++--- .../main/kotlin/PublishingConventionPlugin.kt | 2 +- docs/MIGRATION.md | 8 +- docs/MULTI_MODULE.md | 103 ++++++++++++++++++ .../harrytmthy/stitch/api/StitchInjector.kt | 4 +- 6 files changed, 145 insertions(+), 24 deletions(-) create mode 100644 docs/MULTI_MODULE.md diff --git a/CHANGELOG.md b/CHANGELOG.md index c5eee78..75a4cf4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,13 @@ All notable changes to this project will be documented in this file. -## [1.0.0-rc01] - 2026-04-26 +## [1.0.0] - 2026-04-27 ### Added -- First release candidate for Stitch. \ No newline at end of file +- 🎉 First stable release published to Maven Central. +- Precompiled path for top-tier injection performance. +- Runtime registration path for dynamic module registration. +- Parent-child scope dependencies in the runtime path. +- Custom qualifier support in the precompiled path. +- Migration guide for Dagger 2, Hilt, and Koin. +- Benchmark charts and measurement docs for injection performance, APK size impact, and build-time impact. \ No newline at end of file diff --git a/README.md b/README.md index 2125d21..a0e6b79 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Release](https://img.shields.io/github/v/release/harrytmthy/stitch?include_prereleases&label=release&color=orange&style=flat-square)](https://github.com/harrytmthy/stitch/releases) A Kotlin Multiplatform dependency injection library with a precompiled path and a runtime -registration path, designed to replace the Dagger + Koin split with one consistent model. +registration path, built to combine Dagger-like performance with Koin-like flexibility. ## Why Stitch? @@ -15,7 +15,8 @@ Today, dependency injection tools often force a tradeoff: - **Koin** allows runtime registration, but has slower resolutions and higher APK-size impact. - Using **Dagger 2 + Koin** means carrying two mental models and the combined trade-offs. -Stitch brings both models into one library without the combined trade-offs. +Stitch brings both models into one library without the combined trade-offs, while delivering +**~20× faster injection than Koin** and **10-20% faster injection than Dagger 2**. ## At a Glance @@ -39,7 +40,7 @@ Stitch brings both models into one library without the combined trade-offs. ```kotlin dependencies { - implementation("io.github.harrytmthy:stitch:1.0.0-rc01") + implementation("io.github.harrytmthy:stitch:1.0.0") } ``` @@ -47,13 +48,20 @@ dependencies { ```kotlin dependencies { - implementation("io.github.harrytmthy:stitch:1.0.0-rc01") - compileOnly("io.github.harrytmthy:stitch-annotations:1.0.0-rc01") - ksp("io.github.harrytmthy:stitch-ksp:1.0.0-rc01") + implementation("io.github.harrytmthy:stitch:1.0.0") + compileOnly("io.github.harrytmthy:stitch-annotations:1.0.0") + ksp("io.github.harrytmthy:stitch-ksp:1.0.0") } ``` -You can use either path independently, or combine both in the same project. +This path requires [KSP2](https://kotlinlang.org/docs/ksp-quickstart.html) enabled in every module +that uses annotations: + +```kotlin +plugins { + id("com.google.devtools.ksp") version "2.3.6" +} +``` ## Basic Usage @@ -127,21 +135,17 @@ Annotate any class in the main module with `@StitchRoot`: class SampleApp : Application() ``` -Build, then initialize the generated root graph: +Build, then initialize the generated root graph at your app bootstrap point: ```kotlin StitchInjector.init(StitchSingletonGraph()) ``` -For multi-module projects, other modules should apply this Gradle plugin: +For multi-module projects, see the [Multi-Module Setup guide](docs/MULTI_MODULE.md). -```kotlin -plugins { - id("io.github.harrytmthy.stitch") version "1.0.0-rc01" -} -``` +#### Scopes -Add other scopes, where each scope represents a generated graph: +Define custom scopes, where 1 scope = 1 generated graph: ```kotlin @Scope // Any scope is a child of @Singleton by default @@ -192,6 +196,14 @@ val activityInjector = StitchInjector.getSingletonGraph() activityInjector.inject(this) ``` +To inject fragment-scoped bindings: + +```kotlin +// @Fragment depends on @Activity, so use activityInjector to create fragmentInjector +val fragmentInjector = activityInjector.createInjectorForChildScope("fragment") +fragmentInjector.inject(this) +``` + Unlike Dagger 2, there is no `@Module` or `@Component`. Graphs are generated in the same module as the `@StitchRoot`-annotated class. diff --git a/build-logic/convention/src/main/kotlin/PublishingConventionPlugin.kt b/build-logic/convention/src/main/kotlin/PublishingConventionPlugin.kt index 61c6f9f..d896627 100644 --- a/build-logic/convention/src/main/kotlin/PublishingConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/PublishingConventionPlugin.kt @@ -26,7 +26,7 @@ class PublishingConventionPlugin : Plugin { override fun apply(target: Project) { with(target) { group = "io.github.harrytmthy" - version = "1.0.0-rc01" + version = "1.0.0" pluginManager.apply("org.jetbrains.dokka") pluginManager.apply("com.vanniktech.maven.publish") diff --git a/docs/MIGRATION.md b/docs/MIGRATION.md index 7627d48..2e9f7d8 100644 --- a/docs/MIGRATION.md +++ b/docs/MIGRATION.md @@ -26,7 +26,7 @@ Add the core dependency: ```kotlin dependencies { - implementation("io.github.harrytmthy:stitch:1.0.0-rc01") + implementation("io.github.harrytmthy:stitch:1.0.0") } ``` @@ -44,9 +44,9 @@ Add the runtime, annotation surface, and KSP processor: ```kotlin dependencies { - implementation("io.github.harrytmthy:stitch:1.0.0-rc01") - compileOnly("io.github.harrytmthy:stitch-annotations:1.0.0-rc01") - ksp("io.github.harrytmthy:stitch-ksp:1.0.0-rc01") + implementation("io.github.harrytmthy:stitch:1.0.0") + compileOnly("io.github.harrytmthy:stitch-annotations:1.0.0") + ksp("io.github.harrytmthy:stitch-ksp:1.0.0") } ``` diff --git a/docs/MULTI_MODULE.md b/docs/MULTI_MODULE.md new file mode 100644 index 0000000..5cf69d1 --- /dev/null +++ b/docs/MULTI_MODULE.md @@ -0,0 +1,103 @@ +# Multi-Module Setup for the Precompiled Path + +For the precompiled path, Stitch has two module roles: + +- **Root module**: the module that depends on other modules and has `@StitchRoot`-annotated class +- **Contributor modules**: other modules that contain annotations + +In practice, a typical setup looks like this: + +- `:app` = root module +- `:core`, `:feature:home`, `:feature:profile` = contributor modules + +## 1. Root module setup + +```kotlin +plugins { + id("com.google.devtools.ksp") version "2.3.6" +} + +dependencies { + implementation("io.github.harrytmthy:stitch:1.0.0") + compileOnly("io.github.harrytmthy:stitch-annotations:1.0.0") + ksp("io.github.harrytmthy:stitch-ksp:1.0.0") +} +``` + +and annotate a class with `@StitchRoot`: + +```kotlin +import io.github.harrytmthy.stitch.annotations.StitchRoot + +@StitchRoot +class SampleApp +``` + +After building, initialize the generated root graph at your app bootstrap point: + +```kotlin +StitchInjector.init(StitchSingletonGraph()) +``` + +## 2. Contributor module setup + +```kotlin +plugins { + id("com.google.devtools.ksp") version "2.3.6" + id("io.github.harrytmthy.stitch") version "1.0.0" +} + +dependencies { + implementation("io.github.harrytmthy:stitch:1.0.0") + compileOnly("io.github.harrytmthy:stitch-annotations:1.0.0") + ksp("io.github.harrytmthy:stitch-ksp:1.0.0") +} +``` + +If you don't want to use Stitch Gradle plugin, add this to each module's `build.gradle.kts`: + +```kotlin +ksp.arg("stitch.moduleName", project.path) +``` + +Or manually supply `stitch.moduleName`, ensuring it is globally unique. + +## 3. Sharing scopes across modules + +Each scope represents a global graph, and its declarations can be done in any module: + +```kotlin +@Scope +@Retention(AnnotationRetention.BINARY) +annotation class ActivityScope + +@Scope +@DependsOn(ActivityScope::class) +@Retention(AnnotationRetention.BINARY) +annotation class FragmentScope +``` + +To prevent all modules from depending on a single module that provides the custom scope annotations, +Stitch provides a _decentralized way_ to access any scope via `@Scope("...")`: + +```kotlin +@Scope(name = "ActivityScope") // Case-insensitive +class LoggerImpl @Inject constructor() : Logger +``` + +Any uppercase/lowercase combination of "ActivityScope" points to the same graph: +- `@Scope("activityScope")` +- `@Scope("aCtiviTyScoPE")` +- Or with whitespace: `@Scope(" ActivityScope")` + +You can shorten the name using the same parameter at the declaration site: + +```kotlin +@Scope(name = "activity") +@Retention(AnnotationRetention.BINARY) +annotation class ActivityScope + +// Then later use the shortened version (also case-insensitive) +@Scope(name = "activity") +class LoggerImpl @Inject constructor() : Logger +``` diff --git a/stitch/src/commonMain/kotlin/com/harrytmthy/stitch/api/StitchInjector.kt b/stitch/src/commonMain/kotlin/com/harrytmthy/stitch/api/StitchInjector.kt index 858333b..f254305 100644 --- a/stitch/src/commonMain/kotlin/com/harrytmthy/stitch/api/StitchInjector.kt +++ b/stitch/src/commonMain/kotlin/com/harrytmthy/stitch/api/StitchInjector.kt @@ -32,8 +32,8 @@ import kotlinx.atomicfu.atomic * * Then retrieve injectors to perform injection: * ``` - * val injector = StitchInjector.getSingleton() - * .createInjectorForChildScope("activity", cached = true) + * val injector = StitchInjector.getSingletonGraph() + * .createInjectorForChildScope("activity") * injector.inject(this) * ``` *