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
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Stitch brings both models into one library without the combined trade-offs:
| Runtime binding registration | ✅ | ❌ | ✅ |
| Parent-child scope dependencies | ✅ | ✅ | ❌ |
| Kotlin Multiplatform support | ✅ | ❌ | ✅ |
| Amount of rituals (boilerplate) | ✅ Lowest | ❌ Highest | ✅ Low |
| Injection performance | ✅ Fastest | ✅ Fast | ❌ Slowest |
| APK size impact on larger graphs | ✅ Lowest | ✅ Low | ❌ Highest |
| Build-time impact on larger graphs | ✅ Lower than Dagger | ❌ Highest | ✅ Lowest |
Expand Down Expand Up @@ -97,8 +98,6 @@ Resolve scoped bindings:

```kotlin
val homeActivityScope = activityScope.createScope()
homeActivityScope.open()

val viewModel: HomeViewModel = homeActivityScope.get()

// Cleanup
Expand All @@ -112,8 +111,8 @@ Use `dependsOn()` to establish parent-child dependencies:
val fragmentScope = scope("fragment").dependsOn(activityScope)

// Use it anywhere
val homeActivityScope = activityScope.createScope().apply { open() }
val homeFragmentScope = homeActivityScope.createChildScope(fragmentScope).apply { open() }
val homeActivityScope = activityScope.createScope()
val homeFragmentScope = homeActivityScope.createChildScope(fragmentScope)
val activityViewModel = homeFragmentScope.get<HomeViewModel>()
```

Expand Down
3 changes: 1 addition & 2 deletions stitch/api/stitch.api
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ public final class com/harrytmthy/stitch/api/Scope {
public final fun getParent ()Lcom/harrytmthy/stitch/api/Scope;
public fun hashCode ()I
public final fun isOpen ()Z
public final fun open ()V
}

public final class com/harrytmthy/stitch/api/ScopeKt {
Expand Down Expand Up @@ -146,7 +145,7 @@ public final class com/harrytmthy/stitch/exception/MissingScopeException : com/h
}

public final class com/harrytmthy/stitch/exception/ScopeClosedException : com/harrytmthy/stitch/exception/GetFailedException {
public fun <init> (Lkotlin/reflect/KClass;Lcom/harrytmthy/stitch/api/Qualifier;I)V
public fun <init> (Lkotlin/reflect/KClass;Lcom/harrytmthy/stitch/api/Qualifier;Lcom/harrytmthy/stitch/api/Scope;)V
}

public final class com/harrytmthy/stitch/internal/DefinitionType : java/lang/Enum {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ class Component internal constructor() {

private fun Scope.ensureOpen(type: KClass<*>, qualifier: Qualifier?) {
if (!isOpen()) {
throw ScopeClosedException(type, qualifier, id)
throw ScopeClosedException(type, qualifier, this)
}
}
}
20 changes: 7 additions & 13 deletions stitch/src/commonMain/kotlin/com/harrytmthy/stitch/api/Scope.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,28 +29,17 @@ import kotlinx.atomicfu.atomic
* ```
* val activityScope = scope("activity")
* val instance = activityScope.createScope()
* instance.open()
*
* val viewModel: HomeViewModel = instance.get()
*
* instance.close() // Clears all cached instances for this scope
* ```
*
* A scope must be opened before resolution and closed when no longer used.
* Resolving from a closed scope throws [ScopeClosedException].
*/
class Scope internal constructor(val id: Int, val name: String, val parent: Scope?) {

private val open = atomic(false)

/**
* Opens this scope, allowing bindings to be resolved.
*/
fun open() {
val inner = ScopeManager.idsByScopeName.computeIfAbsent(name) { HashSet() }
inner.add(id)
open.value = true
}
private val open = atomic(true)

/**
* Closes this scope, clearing all cached instances and preventing further resolution.
Expand Down Expand Up @@ -138,7 +127,12 @@ class ScopeRef(val name: String) {
*
* @param parent the parent scope instance, or null for a root scope.
*/
fun createScope(parent: Scope? = null): Scope = Scope(id = ScopeManager.nextId(), name, parent)
fun createScope(parent: Scope? = null): Scope {
val scopeId = ScopeManager.nextId()
val inner = ScopeManager.idsByScopeName.computeIfAbsent(name) { HashSet() }
inner.add(scopeId)
return Scope(scopeId, name, parent)
}

override fun hashCode(): Int = name.hashCode()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,19 @@
package com.harrytmthy.stitch.exception

import com.harrytmthy.stitch.api.Qualifier
import com.harrytmthy.stitch.api.Scope
import kotlin.reflect.KClass

/**
* Thrown when trying to resolve a scoped binding using a scope that is not open.
*
* This guards against using instances after a lifecycle has ended, and against
* accessing scoped bindings before the scope is opened.
*
* To fix:
* - Call `scope.open()` before resolving.
* - Avoid holding on to references after `scope.close()`.
* Thrown when trying to resolve a scoped binding using a scope that is already closed.
* This guards against using instances after a lifecycle has ended.
*/
class ScopeClosedException(
type: KClass<*>,
qualifier: Qualifier?,
scopeId: Int,
) : GetFailedException(type, qualifier, explanation = "Scope with id '$scopeId' is not open!")
scope: Scope,
) : GetFailedException(
type = type,
qualifier = qualifier,
explanation = "Scope '${scope.name}' with id ${scope.id} is already closed!",
)
Loading
Loading