Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
- 🎉 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.
42 changes: 27 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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?

Expand All @@ -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

Expand All @@ -39,21 +40,28 @@ 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")
}
```

### Precompiled path

```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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class PublishingConventionPlugin : Plugin<Project> {
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")
Expand Down
8 changes: 4 additions & 4 deletions docs/MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
```

Expand All @@ -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")
}
```

Expand Down
103 changes: 103 additions & 0 deletions docs/MULTI_MODULE.md
Original file line number Diff line number Diff line change
@@ -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
```
Original file line number Diff line number Diff line change
Expand Up @@ -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)
* ```
*
Expand Down
Loading