How to scope a Usecase to a Feature / Activity in Koin

1.2k views Asked by At

I'm maintaining a large app (mostly) using a "one feature one activity"-architecture.

Now i'd like to scope a usecase, so it lives as long as the activity, something like this:

// koin module
scope<MyFeatureActivity> {
    viewModel { MyFeatureActivityViewModel() }
    viewModel { MyFeatureFragmentAViewModel(usecase = get()) }
    viewModel { MyFeatureFragmentBViewModel(usecase = get()) }
    scoped { MyFeatureUseCase() }
}

// fragments
class FeatureAFragment: AppCompatDialogFragment(){

    private val viewModel by viewModel<MyFeatureFragmentAViewModel>()

    ....
}

// activity
class MyFeatureActivity : ScopeActivity() { ... }

However, this doesn't work. When launching MyFeatureFragmentA from MyFeatureActivity it's throwing an Exception:

org.koin.core.error.NoBeanDefFoundException: 
|- No definition found for class:'MyFeatureAViewModel'. Check your definitions!

What am i doing wrong?

Please note: I would not like to just skip scopes and make the usecase a single (or a factory), since it actually stores some data relevant to only this activity: The data should be kept while we're in this feature, but dismissed when leaving it.

1

There are 1 answers

0
Re'em On

you will need to set both activity scope and fragment scope for this.

complete code snippet:


val myFeatureModule = module {
  scope<MyFeatureActivity> {
    scoped { MyFeatureUseCase() } // `scoped{}` = singleton per activity
    viewModel { MyFeatureActivityViewModel() }
    viewModel { MyFeatureFragment1ViewModel(usecase = get()) }
    viewModel { MyFeatureFragment2ViewModel(usecase = get()) }
  }
}

// now declare both the activity and the fragment as `KoinScopeComponent`.
// have their scope using standard koin-based provided scopes (see below)
// this way, the fragment scope will extend activity scope and koin bindings will work

class MyFeatureActivity: AppCompatActivity(), KoinScopeComponent {

   override val scope: Scope by activityScope()

   // koin bindings now will work on the activity. for example:
   val viewModel by viewModel<MyFeatureActivityViewModel>()
   ...
}

class MyFeatureFragment1: Fragment(), KoinScopeComponent {
  override val scope: Scope by fragmentScope()

  // koin bindings now will work on the fragment
  //
  // you can use `by viewModel<ViewModelClassType>` to get a viewmodel lifecycled to this fragment, 
  // or you can use `by sharedViewModel<ViewModelClassType>` to get a viewmodel lifecycled to the host activity
  

  // example for private viewModel (no one else is holding a reference to it):
  val viewModel by viewModel<MyFeatureFragment1ViewModel>() // private VM for the fragment

  // example of a view-model that the host activity is holding.
  // you can communicate with the activity (using e.g. live-data) 
  // or communicate with other fragments inside this activity, 
  // by using this viewModel:
  val activityViewModel by sharedViewModel<MyFeatureActivityViewModel>()
  
  ...
}