'Koin 3.2/Compose - injecting an activity-scoped viewmodel from a composable

I have scoped a viewmodel to an activity like this:

    scope<MyActivity> {
      scoped<UseCase1> { UseCase1Impl(get()) }
      scoped<UseCase2> { UseCase2Impl(get()) }
      viewModel { parameters -> MyViewModel(parameters.get(), get(), get()) }
    }

In my activity I cannot access the viewmodel inside a composable with this call:

        setContent {

      val viewModel: MyViewModel by org.koin.androidx.compose.viewModel {
        ParametersHolder(mutableListOf("1"))
      }
      viewModel.doSomething() 
}

UNLESS I also do this outside the composable:

   private val viewModel by viewModel<CameraNameViewModel> { parametersOf("1") }

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    viewModel // If I comment this, it crashes

   // setContent etc
}

The error is

 Caused by: org.koin.core.error.NoBeanDefFoundException: |- No definition found for class:'com.anonymised.path.to.MyViewModel'. Check your definitions!

Deep-diving with the debugger, it looks like the activities mViewmodelStore property is only populated when accessed outside the composable. I do have a workaround in that I can just inject every viewmodel in my top-level activity, but it doesn't seem right basically injecting it twice.



Solution 1:[1]

So you have two options:


Option 1: If your both your activity and your composable need the viewModel:

  • Inject your viewModel into your activity like you already have

  • just pass the viewModel to your composable

    class YourActivity : AppCompatActivity() {
        private val viewModel by viewModel<CameraNameViewModel> { parametersOf("1") }
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
    
            setContent {
                YourComposeScreen(viewModel)
            }
        }
    }
    
    @Composable fun YourComposeScreen(viewModel: CameraNameViewModel){
        /*your composable */
    }
    

Option 2: you only need your viewModel in your composable:

  • don't inject the viewModel into your composable

  • just use koins Compose-Syntax to inject the viewModel to your composable

    class YourActivity : AppCompatActivity() {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
    
            setContent {
                YourComposeScreen() // <- we do not need any parameter here
            }
        }
    }
    
    @Composable fun YourComposeScreen(
        viewModel: CameraNameViewModel = getViewModel(parameters = { parametersOf("1") })
    ){
        /*your composable */
    }
    

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 m.reiter