androidandroid-viewmodeldagger-hiltdynamic-feature-module

How to create ViewModel in dynamic feature module with Dagger Hilt?


Trying to create ViewModel in a dynamic feature module with private val viewModel: PostDetailViewModel by viewModels()

in fragment

class PostDetailFragment : DynamicNavigationFragment<FragmentPostDetailBinding>() {

    private val viewModel: PostDetailViewModel by viewModels()
    
    override fun getLayoutRes(): Int = R.layout.fragment_post_detail

    override fun bindViews() {
        // Get Post from navigation component arguments
        val post = arguments?.get("post") as Post
        dataBinding.item = post
        viewModel.updatePostStatus(post)
        
    }

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

    private fun initCoreDependentInjection() {

        val coreModuleDependencies = EntryPointAccessors.fromApplication(
            requireActivity().applicationContext,
            DomainModuleDependencies::class.java
        )

        DaggerPostDetailComponent.factory().create(
            coreModuleDependencies,
            requireActivity().application
        )
            .inject(this)

    }
}

results error

Caused by: java.lang.InstantiationException: java.lang.Class<com.x.post_detail.PostDetailViewModel> has no zero argument constructor

it works in any fragment in app module but not working in dynamic feature modules. What's the proper way to add ViewModels to dynamic feature modules? Should i create ViewModels in app module with a ViewModelFactory and get them from app module?


Solution

  • Based on this official github posts

    There's documentation on Hilt and DFM now at https://developer.android.com/training/dependency-injection/hilt-multi-module#dfm

    In general though, because we're built off of subcomponents and monolithic components you won't be able to use the standard Hilt mechanisms like @AndroidEntryPoint with DFM.

    Unfortunately, no. @ViewModelInject uses the Hilt ActivityRetainedComponent which is monolithic, so any @ViewModelInject class in your DFM won't be recognized.

    it seems that injecting to a ViewModel only with @ViewModelInject and by viewModels() in a dynamic feature module is not possible as of now.

    Based on plaid app i rebuilt my Dagger module in dynamic feature module as

    @InstallIn(FragmentComponent::class)
    @Module
    class PostDetailModule {
    
        @Provides
        fun providePostDetailViewModel(fragment: Fragment, factory: PostDetailViewModelFactory) =
            ViewModelProvider(fragment, factory).get(PostDetailViewModel::class.java)
    
        @Provides
        fun provideCoroutineScope() = CoroutineScope(Dispatchers.Main.immediate + SupervisorJob())
    
    }
    

    And ViewModel and ViewModelFactory are

    class PostDetailViewModel @ViewModelInject constructor(
        private val coroutineScope: CoroutineScope,
        private val getPostsUseCase: UseCase
    ) : ViewModel() {
     
        // Do other things
    }
    
    class PostDetailViewModelFactory @Inject constructor(
        private val coroutineScope: CoroutineScope,
        private val getPostsUseCase: UseCase
    ) : ViewModelProvider.Factory {
    
        @Suppress("UNCHECKED_CAST")
        override fun <T : ViewModel> create(modelClass: Class<T>): T {
            if (modelClass != PostDetailViewModel::class.java) {
                throw IllegalArgumentException("Unknown ViewModel class")
            }
            return PostDetailViewModel(
                coroutineScope,
                getPostsUseCase
            ) as T
        }
    }
    

    And injected to fragment in dynamic feature module

    class PostDetailFragment : Fragment() {
    
        @Inject
        lateinit var viewModel: PostDetailViewModel
    
    
        override fun onCreate(savedInstanceState: Bundle?) {
            initCoreDependentInjection()
            super.onCreate(savedInstanceState)
        }
    
        private fun initCoreDependentInjection() {
    
            val coreModuleDependencies = EntryPointAccessors.fromApplication(
                requireActivity().applicationContext,
                DomainModuleDependencies::class.java
            )
    
            DaggerPostDetailComponent.factory().create(
                dependentModule = coreModuleDependencies,
                fragment = this
            )
                .inject(this)
        }
    }