Skip to content

Calling scoped suspending provider should create singleton instance #337

@sdipendra

Description

@sdipendra

Hi,

kotlin-inject currently doesn't allow scoping suspending provider method.

    @Provides
    @NetworkScope
    protected suspend fun api(service: Service): Api = service.connect()

Expectation: scoped suspending provider should create a single instance of the returned object.
Observation: Adding scope to suspending provider method causes compilation to fail with error: "Suspension functions can be called only within coroutine body" & removing the scope passes compilation but creates new object on every invocation.

Below is the failing test for the same:

import kotlinx.coroutines.runBlocking
import me.tatarka.inject.annotations.Component
import me.tatarka.inject.annotations.Inject
import me.tatarka.inject.annotations.Provides
import me.tatarka.inject.annotations.Scope
import org.junit.jupiter.api.Test
import java.util.concurrent.atomic.AtomicInteger
import kotlin.test.assertEquals

@Scope
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER)
annotation class NetworkScope

@Component
@NetworkScope
internal abstract class NetworkComponent(@get:Provides protected val apiInstanceCounter: AtomicInteger) {
    @NetworkScope
    abstract suspend fun api(): Api

    @Provides
    // Note: Uncommenting below gives error: Suspension functions can be called only within coroutine body
    // @NetworkScope
    protected suspend fun api(service: Service): Api = service.connect()

    protected val DummyNetworkService.bind: Service
        @Provides get() = this
}

interface Api

interface Service {
    suspend fun connect(): Api
}

@Inject
class DummyNetworkService(private val apiInstanceCounter: AtomicInteger) : Service {
    override suspend fun connect(): Api {
        apiInstanceCounter.incrementAndGet()

        return object : Api {}
    }
}

internal class KotlinInjectTest {
    @Test
    fun `calling scoped suspending provider should create singleton instance`() {
        val apiInstanceCounter = AtomicInteger(0)

        val networkComponent = NetworkComponent::class.create(apiInstanceCounter)

        runBlocking {
            val apiInstance0 = networkComponent.api()
            val apiInstance1 = networkComponent.api()

            assertEquals(apiInstance0, apiInstance1)
        }
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions