scoped injection with koin 3

1.5k views Asked by At

I am trying to upgrade my koin usage from 2.1.6 -> 3.0.2 and am having troubles with the scoped injections.

I have MVPs where the Activity/Fragment is the view and i want to inject the view in the presenter.

so i have

module {
    scope(named<MainActivity>()) {
    scoped<View> { getSource() }
    scoped<Presenter> {
         MainPresenter(
             view = get()
         )
    }
}

in 2.1.6 i used to used to do this and all was fine:

class MainActivity :
    AppCompatActivity(),
    MainContract.View {

    private val presenter: MainContract.Presenter by currentScope.inject()
    ...
}

and then in MainActivity i NOW have:

class MainActivity :
    AppCompatActivity(),
    MainContract.View,
    AndroidScopeComponent {
    override val scope : Scope by activityScope()
    private val presenter: MainContract.Presenter by scope.inject()

...
}

and Presenter:

   class MainPresenter(
       private val view: MainContract.View
   ){
       ...
   }

but it cannot get the source object and i get the error:

Instance creation error : could not create instance for [Single:'uk.co.sentinelweb.cuer.app.ui.main.MainContract$View',scope:q:'uk.co.sentinelweb.cuer.app.ui.main.MainActivity']: java.lang.IllegalStateException: Can't use Scope source for uk.co.sentinelweb.cuer.app.ui.main.MainContract$View - source is:null

(i.e. when it tries to create the presenter it can't find the scoped MainActivity)

this is the existing code (using 2.1.6) https://github.com/sentinelweb/cuer/blob/develop/app/src/main/java/uk/co/sentinelweb/cuer/app/ui/main/MainActivity.kt

Have i got a lot more re-writing to do here? I am struggling to find a good example for scoped injection in the koin docs and a lot of it seems old. A lot of projects seem not to use scoping.

So if anyone can tell me what wrong here or point me to a decent example of something similar id appreciate it a lot!

1

There are 1 answers

0
siliconeagle On

So it seems for the lifecycle aware extension methods it just doesnt set the scope variable - perhaps they are paranoid about mempory leaks but since the scope is cleared and on the destroy lifecycle method - this shouldnt be a problem really.

My solution was to just make new extension methods which actually just pass in the source - I am not sure why this would be a problem. There is an issue here for it https://github.com/InsertKoinIO/koin/issues/851

package xxx

import android.app.Service
import androidx.activity.ComponentActivity
import androidx.fragment.app.Fragment
import androidx.lifecycle.LifecycleOwner
import org.koin.android.scope.AndroidScopeComponent
import org.koin.android.scope.createScope
import org.koin.android.scope.getScopeOrNull
import org.koin.androidx.scope.LifecycleScopeDelegate
import org.koin.core.Koin
import org.koin.core.component.getScopeId
import org.koin.core.component.getScopeName
import org.koin.core.context.GlobalContext
import org.koin.core.context.KoinContext
import org.koin.core.scope.Scope
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty

/** copied from org.koin.androidx.scope.FragmentExt but scope wont link as fragment is not attached */
fun ComponentActivity.activityScopeWithSource() = LifecycleScopeWithSourceDelegate(this)

/** copied from org.koin.androidx.scope.FragmentExt but scope wont link as fragment is not attached */
fun Fragment.fragmentScopeWithSource() = LifecycleScopeDelegate(this) { koin: Koin ->
    koin.createScope(getScopeId(), getScopeName(), this)
}

/** links the fragment scope to the activity scope */
fun Fragment.linkScopeToActivity() {
    (this as AndroidScopeComponent).scope.linkTo((requireActivity() as AndroidScopeComponent).scope)
}

/** copied from org.koin.android.scope.ServiceExtKt  */
fun Service.serviceScopeWithSource() = lazy { getScopeOrNull() ?: createScope(this) }

/** wraps org.koin.androidx.scope.LifecycleScopeDelegate - to add source  */
class LifecycleScopeWithSourceDelegate(
    val lifecycleOwner: LifecycleOwner,
    koinContext: KoinContext = GlobalContext,
    createScope: (Koin) -> Scope = { koin: Koin ->
        koin.createScope(
            lifecycleOwner.getScopeId(),
            lifecycleOwner.getScopeName(),
            lifecycleOwner
        )
    },
) : ReadOnlyProperty<LifecycleOwner, Scope> {

    private val _lifecycleDelegate = LifecycleScopeDelegate(lifecycleOwner, koinContext, createScope)

    override fun getValue(thisRef: LifecycleOwner, property: KProperty<*>): Scope {
        return _lifecycleDelegate.getValue(thisRef, property)
    }
}