InsertKoinIO / koin

Koin - a pragmatic lightweight dependency injection framework for Kotlin & Kotlin Multiplatform
https://insert-koin.io
Apache License 2.0
8.77k stars 695 forks source link

activityViewModel with scoped parameters can't be injected #1825

Open fleficher opened 3 months ago

fleficher commented 3 months ago

Describe the bug

When a shared ViewModel that has parameters gets injected in a Fragment, Koin fails to retrieve the instance:

Caused by org.koin.core.error.InstanceCreationException: Could not create instance for '[Factory:'hm.a']'
       at org.koin.core.error.InstanceCreationException.<init>(InstanceCreationException.java:23)
       at org.koin.core.instance.InstanceFactory.create(InstanceFactory.kt:57)
       at org.koin.core.instance.FactoryInstanceFactory.get(FactoryInstanceFactory.kt:38)
       at org.koin.core.registry.InstanceRegistry.resolveInstance$koin_core(InstanceRegistry.java:109)
       at org.koin.core.scope.Scope.resolveValue(Scope.kt:247)
       at org.koin.core.scope.Scope.resolveInstance(Scope.kt:233)
       at org.koin.core.scope.Scope.get(Scope.kt:212)
       at org.koin.androidx.viewmodel.factory.KoinViewModelFactory.create(KoinViewModelFactory.kt:25)
       at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.kt:184)
       at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.kt:150)
       at org.koin.androidx.viewmodel.GetViewModelKt.resolveViewModel(GetViewModel.kt:43)
       at org.koin.androidx.viewmodel.GetViewModelKt.resolveViewModel$default(GetViewModel.kt:28)
Caused by org.koin.core.error.NoParameterFoundException: Can't get injected parameter #0 from DefinitionParameters[] for type 'xxx'
       at org.koin.core.error.NoParameterFoundException.<init>(NoParameterFoundException.java:23)
       at org.koin.core.parameter.ParametersHolder.elementAt(ParametersHolder.kt:45)
       at org.koin.androidx.viewmodel.parameter.AndroidParametersHolder.access$elementAt$s975513686(AndroidParametersHolder.kt:10)
       at org.koin.androidx.viewmodel.parameter.AndroidParametersHolder$elementAt$1.invoke(AndroidParametersHolder.kt:16)
       at org.koin.androidx.viewmodel.parameter.AndroidParametersHolder.createSavedStateHandleOrElse(AndroidParametersHolder.kt:26)
       at org.koin.androidx.viewmodel.parameter.AndroidParametersHolder.elementAt(AndroidParametersHolder.kt:16)

It seems to happen if the activity is implementing AndroidScopeComponent with activityRetainedScope() scope.

Koin module and version:

was working well on 3.5.0

Snippet or Sample project to help reproduce


// VM
class WeatherViewModel(private val id: Anything) : ViewModel()

// inside Koin module
viewModel<WeatherViewModel> { (
    id: Anything
) ->
    WeatherViewModel(id)
}

scope<WeatherActivity> {
    scoped<Anything> {
       Anything()
    }
}

// Activity
class WeatherActivity : AppCompatActivity(), AndroidScopeComponent {
    override val scope: Scope by activityRetainedScope()

    private val anything: Anything by inject()

    private val weatherViewModel by viewModel<WeatherViewModel> {
        parametersOf(anything)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        weatherViewModel.run {}
        // attach fragment
    }
}

// Fragment instantiated by the activity
class WeatherListFragment : Fragment() {
    private val weatherViewModel by activityViewModel<WeatherViewModel>()
}
guilhermemagro commented 2 months ago

On Koin 3.5.6 I am getting the same error Caused by org.koin.core.error.NoParameterFoundException: Can't get injected parameter #0 from DefinitionParameters[] for type 'xxx' and I am also using an Activity with AndroidScopeComponent but I am not using ViewModel and activityRetainedScope().

I could reproduce de error with this test on the Koin lib:

class ScopeWithParametersTest : AutoCloseKoinTest() {

    interface MyContract {
        interface View
        interface Presenter
    }
    class MyView : AppCompatActivity(), AndroidScopeComponent, MyContract.View {
        override val scope by lazy { getKoin().getScopeOrNull(getScopeId()) ?: getKoin().createScope(getScopeId(), getScopeName(), this) }
        //  It also breaks with activityRetainedScope:
        // override val scope by activityRetainedScope()
    }

    class MyPresenter(private var view: MyContract.View?) : MyContract.Presenter
    interface FacadeContract
    class MyFacade(private var presenter: MyContract.Presenter?) : FacadeContract

    object MyModule {
        val instance = module {
            scope(named<MyView>()) {
                scoped<FacadeContract> {
                    MyFacade(presenter = get())
                }
                scoped<MyContract.Presenter> { (view: MyContract.View) ->
                    MyPresenter(view = view)
                }
            }
        }
    }

    @get:Rule
    val mockProvider = MockProviderRule.create { clazz ->
        mockkClass(clazz)
    }

    @Test
    fun `check koin definitions with success`() {
        startKoin {
            modules(
                MyModule.instance
            )
        }.checkModules()
    }
}

I think the problem is something related to classes with parameters inside scopes.

arnaudgiuliani commented 2 weeks ago

I could test scoped object to activity, injected in shared VM: https://github.com/InsertKoinIO/koin/commit/9902222e93cdadb3cb86fb161ee52a87e39ed681

@fleficher can you test upgrade to 3.5.6?

fleficher commented 1 week ago

Hi @arnaudgiuliani , I confirm that I don't experience the crash anymore using version 3.5.6 👍 Thanks!