silaros88
silaros88

Reputation: 65

Kotlin Dagger 2 Sharing ViewModel between Activity and Fragment

As mentioned in the title I've got a problem with Dagger 2 injection. I have a single activity and multiple fragments. I'm trying to share activity's ViewModel with every child fragment. I based my solution on Google's Android Architecture ToDo sample. I've created ViewModelFactory as

ViewModelFactory

If you jump to the link you will see that in their solution there's a separate ViewModel for every activity and fragment. They are not showing how to deal with scenarios like mine. My implementation of ActivityModule looks like:

 @Module
 abstract class SampleModule {
    @ContributesAndroidInjector(
            modules = [
                ViewModelBuilder::class
            ]
    )
    internal abstract fun sampleActivity(): SampleActivity

    @Binds
    @IntoMap
    @ViewModelKey(SampleViewModel::class)
    abstract fun bindViewModel(viewModel: SampleViewModel): ViewModel
}

My activity extends DaggerAppCompatActivity and fragment DaggerFragment and as follows my injection of view model looks simple as

class SampleActivity : DaggerAppCompatActivity() {

   @Inject
   lateinit var viewModel: SampleViewModel
   ...

I can't find a web solution to my problem. I'm a pretty novice user of Dagger. I've tried to implement Subcomponent but still, it's not working because all the examples I have searched so far didn't use DaggerApplication, DaggerAppCompatActivity and my way of injection. Please suggest any solution or if a subcomponent way is right please show me how to do it if it's possible in my current architecture.

Thank you very much in advance.

Upvotes: 2

Views: 2446

Answers (2)

Pallab Banerjee
Pallab Banerjee

Reputation: 61

I solved the problem with a slightly different approach. Since the fragments and the activity both have dagger modules. In the ActivityModule I am providing the sharedViemodel as below

@Module
class ActivityModule(private val activity: AppCompatActivity) 
{

  @Provides
  fun provideMainSharedVieModel() : MainSharedViewModel = 
     ViewModelProvider(activity).get(MainSharedViewModel::class.java)
 }

And in my fragment module I am again providing the same viemodel as below:

@Module
class FragmentModule(private val fragment: Fragment) 
{

  @Provides
  fun provideMainSharedVieModel() : MainSharedViewModel = 
     ViewModelProvider(fragment.activity!!).get(MainSharedViewModel::class.java)
 }

Since the ViewModels are stored in a map with the activity or fragments as the key, hence providing the sharedViewModel with "fragment.activity!!" in the Fragment module will not create a new instance of the viewmodel , it will just the return the already instantiated shared viewmodel to the fragment.

Upvotes: 0

cmpeguerog
cmpeguerog

Reputation: 657

@silaros88 I was facing yor same issue, share a ViewModel between multiple fragmnets in a single Activity application, and i solved playing with the ViewmModelStoreOwner.

Steps to fix your issue.

  • See here TasksFragment.kt how they inject the ViewModelProvider.Factory instead of the ViewModel
  • Retrieve the desire ViewModel using one of this two options:

    • viewModels<SharedDesireViewModel> (requireActivity()) { Injected ViewModelProvider.Factory }
    • ViewModelProvider(requireActivity(), Injected ViewModelProvider.Factory ).get(SharedDesireViewModel::class.java)

Examples:

Option #1:

FragmentA.kt

class FragmentA: DaggerFragment() {

    @Inject
    lateinit var viewModelProviderFactory: ViewModelProvider.Factory

    private val mainViewModel: MainViewModel by viewModels({requireActivity()}) { viewModelProviderFactory }

    ......

FragmentB.kt

class FragmentB: DaggerFragment() {

    @Inject
    lateinit var viewModelProviderFactory: ViewModelProvider.Factory

    private val mainViewModel: MainViewModel by viewModels({requireActivity()}) { viewModelProviderFactory }

    ......

Option #2:

FragmentA.kt

class FragmentA: DaggerFragment() {

    @Inject
    lateinit var viewModelProviderFactory: ViewModelProvider.Factory

    private val mainViewModel: MainViewModel by lazy {
    ViewModelProvider(requireActivity(), viewModelProviderFactory)
        .get(MainViewModel::class.java)
    }

    ......

FragmentB.kt

class FragmentB: DaggerFragment() {

    @Inject
    lateinit var viewModelProviderFactory: ViewModelProvider.Factory

    private val mainViewModel: MainViewModel by lazy {
    ViewModelProvider(requireActivity(), viewModelProviderFactory)
        .get(MainViewModel::class.java)
    }

    ......

Upvotes: 1

Related Questions