3

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?

Thracian
  • 43,021
  • 16
  • 133
  • 222
  • 1
    Does this helps? https://code.luasoftware.com/tutorials/android/android-app-bundle-viewmodel-and-helper-class/ – Skizo-ozᴉʞS ツ Aug 31 '20 at 13:35
  • 1
    @Skizo-ozᴉʞS, i'm looking into it. What makes it work with ViewModel in fragment with `by viewModels` in feature module. I see that `ViewModel` is created inside an Activity, is that in a dynamic feature module or app module? Because i wasn't able create ViewModel in fragment in dynamic feature module. – Thracian Aug 31 '20 at 16:10

1 Answers1

3

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)
    }
}
Thracian
  • 43,021
  • 16
  • 133
  • 222
  • How to do if I want to make ViewModel for an Activity? – imn Aug 25 '21 at 07:08
  • @imnithish You do it the same way it's done for fragments in dynamic feature modules. Replace `FragmentComponent ` with `ActivityComponent` and do injection that is done in `initCoreDependentInjection` in *Activity*'s onCreate method. – Thracian Aug 25 '21 at 07:13