7

I'm trying to make a shared injected view model between a fragment and an activity using the Jetpack tutorial.

The shared view model is successfully injected into the parent MyActivity but when the child is rendered, the application crashes due to dependency injection failure. I have provided the code below that created the issue.

Providing the Session Manager:

@InstallIn(ApplicationComponent::class)
@Module
class AppModule {
    @Provides
    @Singleton
    fun provideSessionManager(
        networkClient: NetworkClient
    ): SessionManager {
        return SessionManager(networkClient)
    }
}

To be injected into the Shared View Model:

class SharedViewModel @ViewModelInject constructor(
    private var sessionManager: SessionManager
) : ViewModel() {

    var name = MutableLiveData<String>("Shared View Model")
}

And is used by both a parent activity and child fragment.

class MyActionFragment() : Fragment() {
    private val viewModel: SharedViewModel by viewModels()
    override fun onActivityCreated(savedInstanceState: Bundle?) {
       super.onActivityCreated(savedInstanceState)
       Timber.d("View Model Name 1: ${viewModel.name.value}") // This line crashes
    }
}
class MyActivity : AuthenticatedBaseActivity() {
    private val viewModel: SharedViewModel by viewModels()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Timber.d("View Model Name 2: ${viewModel.name.value}") // This line prints
    }
}

However, when the code is run, notice the activity created the ViewModel and accessed its values, but when the fragment tried to do the same, the application crashes:

**D/MyActivity: View Model Name 2: Shared View Model**
E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.xxx.xxx, PID: 16630
    java.lang.RuntimeException: Cannot create an instance of class com.xxx.xxx.ui.main.SharedViewModel
        at androidx.lifecycle.ViewModelProvider$NewInstanceFactory.create(ViewModelProvider.java:221)
        at androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.java:278)
        at androidx.lifecycle.SavedStateViewModelFactory.create(SavedStateViewModelFactory.java:106)
        at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:185)
        at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:150)
        at androidx.lifecycle.ViewModelLazy.getValue(ViewModelProvider.kt:54)
        at androidx.lifecycle.ViewModelLazy.getValue(ViewModelProvider.kt:41)
        at com.xxx.xxx.ui.main.MyActionFragment.getViewModel(Unknown Source:2)
        at com.xxx.xxx.ui.main.MyActionFragment.onActivityCreated(**MyActionFragment.kt:140**)
        at androidx.fragment.app.Fragment.performActivityCreated(Fragment.java:2718)

Additionally, when I remove the Hilt dependency injected sessionManager the fragment and view model are created without an issue.

Followed this post with no luck.

Any help on Hilt view model dependency injection with a shared model would be extremely appreciated!! Thanks!

Bartek Lipinski
  • 30,698
  • 10
  • 94
  • 132
Neal Soni
  • 556
  • 1
  • 5
  • 13

3 Answers3

21

You can use extension function in Fragment:

class MyFragment: Fragment() {
    private val viewModel: SharedViewModel by activityViewModels()
}

And in Activity:

class MyActivity : Activity() {
    private val viewModel: SharedViewModel by viewModels()
}
Yeldar Nurpeissov
  • 1,786
  • 14
  • 19
  • Then, what would be the case if two fragments use the same viewmodel? – Marimuthu May 18 '22 at 08:01
  • @Marimuthu viewModel can be shared across fragments in the same activity, if you use this solution. In the second fragment you just need to do what you have done in first fragment, get it by using `by activityViewModels()`. The viewModel if defined in activity, so the owner of the viewModel is activity, not fragments. Also I recommend to store only states of views in the viewModel, and move other things to inner layers, like domain and data. But this is different story. – Yeldar Nurpeissov May 18 '22 at 15:38
8

You must provide all dependency , In your case NetworkClient not provided

@Module
@InstallIn(ApplicationComponent::class)
object AppModule {

    @Singleton
    @Provides
    fun provideSessionManager(
        networkClient: NetworkClient
    ): SessionManager = SessionManager(networkClient)

    @Singleton
    @Provides
    fun provideNetworkClient() = NetworkClient()
}

In the Activity or Fragment use @AndroidEntryPoint

@AndroidEntryPoint
class MyActionFragment() : Fragment()



@AndroidEntryPoint
class MyActivity : AuthenticatedBaseActivity()
cascal
  • 2,943
  • 2
  • 17
  • 19
Saeid Lotfi
  • 2,043
  • 1
  • 11
  • 23
  • 1
    My Problem was, I was using viewmodel in Fragment and on Fragment i didn't mention @AndroidEntryPoint. I mentioned that on Activity but not on Fragment. – Sharad Aug 06 '20 at 06:23
8

To share data between activity and fragments. use the below code. Hilt doc didn't work for me also.

In Activity

    private val vm by viewModels<StartVM>()

In Fragment

private val vm: StartVM by lazy {
    obtainViewModel(requireActivity(), StartVM::class.java, defaultViewModelProviderFactory)
}

Kotlin extension

fun <T : ViewModel> Fragment.obtainViewModel(owner: ViewModelStoreOwner,
    viewModelClass: Class<T>,
    viewmodelFactory: ViewModelProvider.Factory
) =
    ViewModelProvider(owner, viewmodelFactory).get(viewModelClass)
Shaon
  • 2,496
  • 26
  • 27