title: Dagger/Hilt 2.32-alpha Hilt Jetpack 1.0.0-alpha03 + Dynamic Feature + ViewModel + SavedStateHandle and dagger.assisted.AssistedInject
body: <p>I created a sample project to use Dagger/Hilt with dynamic feature and ViewModel and everything was working great with <code>Dagger/Hilt 2.29.1-alpha</code> and <code>Hilt Jetpack 1.0.0-alpha02</code> you can check the source code <a href="https://github.com/IbrahimDisouki/CurrencyConverter/tree/develop" rel="nofollow noreferrer">here</a>. Now a new version of Dagger/Hilt is here which is <code>Dagger/Hilt 2.32-alpha Hilt Jetpack 1.0.0-alpha03</code> and you can check the source code <a href="https://github.com/IbrahimDisouki/CurrencyConverter/tree/update_hilt" rel="nofollow noreferrer">here</a>. In the <code>app</code> module I created the following class to be a generic view model factory</p>
  <pre><code>package com.ibrahim.currencyconverter.di
  
  import android.annotation.SuppressLint
  import android.os.Bundle
  import androidx.hilt.lifecycle.ViewModelAssistedFactory
  import androidx.lifecycle.AbstractSavedStateViewModelFactory
  import androidx.lifecycle.SavedStateHandle
  import androidx.lifecycle.SavedStateViewModelFactory
  import androidx.lifecycle.ViewModel
  import androidx.savedstate.SavedStateRegistryOwner
  import javax.inject.Provider
  
  class DFMSavedStateViewModelFactory(
      owner: SavedStateRegistryOwner,
      defaultArgs: Bundle?,
      private val delegateFactory: SavedStateViewModelFactory,
      private val viewModelFactories: @JvmSuppressWildcards Map&lt;String, Provider&lt;ViewModelAssistedFactory&lt;out ViewModel&gt;&gt;&gt;
  ) : AbstractSavedStateViewModelFactory(owner, defaultArgs) {
  
      @SuppressLint(&quot;RestrictedApi&quot;)
      override fun &lt;T : ViewModel?&gt; create(
          key: String,
          modelClass: Class&lt;T&gt;,
          handle: SavedStateHandle
      ): T {
          val factoryProvider = viewModelFactories[modelClass.name]
              ?: return delegateFactory.create(&quot;$KEY_PREFIX:$key&quot;, modelClass)
          @Suppress(&quot;UNCHECKED_CAST&quot;)
          return factoryProvider.get().create(handle) as T
      }
  
      companion object {
          private const val KEY_PREFIX = &quot;androidx.hilt.lifecycle.HiltViewModelFactory&quot;
      }
  }
  </code></pre>
  <p><code>FragmentViewModelModule</code> class in the <code>app</code> module to provide the view model factory</p>
  <pre><code>package com.ibrahim.currencyconverter.di
  
  import android.app.Application
  import androidx.fragment.app.Fragment
  import androidx.hilt.lifecycle.ViewModelAssistedFactory
  import androidx.lifecycle.SavedStateViewModelFactory
  import androidx.lifecycle.ViewModel
  import dagger.Module
  import dagger.Provides
  import dagger.hilt.InstallIn
  import dagger.hilt.android.components.FragmentComponent
  import javax.inject.Provider
  
  @Module
  @InstallIn(FragmentComponent::class)
  object FragmentViewModelModule {
  
      @Provides
      fun provideSavedStateViewModelFactory(
          application: Application,
          fragment: Fragment,
          viewModelFactories: @JvmSuppressWildcards Map&lt;String, Provider&lt;ViewModelAssistedFactory&lt;out ViewModel&gt;&gt;&gt;,
      ): DFMSavedStateViewModelFactory {
          val defaultArgs = fragment.arguments
          val delegate = SavedStateViewModelFactory(application, fragment, defaultArgs)
          return DFMSavedStateViewModelFactory(fragment, defaultArgs, delegate, viewModelFactories)
      }
  }
  </code></pre>
  <p><code>AppDependencies</code> class in the <code>app</code> model to expose required dependencies by the dynamic feature module which is the <code>home</code> dynamic feature module</p>
  <pre><code>package com.ibrahim.currencyconverter.di
  
  import android.app.Application
  import dagger.hilt.EntryPoint
  import dagger.hilt.InstallIn
  import dagger.hilt.components.SingletonComponent
  import retrofit2.Retrofit
  
  @EntryPoint
  @InstallIn(SingletonComponent::class)
  interface AppDependencies {
      fun exposeApplication(): Application
      fun exposeRetrofit(retrofit: Retrofit): Retrofit
  }
  </code></pre>
  <p>Then in the <code>home</code> dynamic feature module, I created <code>HomeComponent</code> class to inject the <code>HomeFragment</code></p>
  <pre><code>package com.ibrahim.home
  
  import androidx.fragment.app.Fragment
  import com.ibrahim.currencyconverter.di.AppDependencies
  import dagger.BindsInstance
  import dagger.Component
  import kotlinx.coroutines.ExperimentalCoroutinesApi
  import javax.inject.Singleton
  
  @Singleton
  @Component(dependencies = [AppDependencies::class], modules = [HomeModule::class])
  interface HomeComponent {
  
      @ExperimentalCoroutinesApi
      fun inject(fragment: HomeFragment)
  
      fun fragment(): Fragment
  
      @Component.Builder
      interface Builder {
          fun fragment(@BindsInstance fragment: Fragment): Builder
          fun appDependencies(appDependencies: AppDependencies): Builder
          fun build(): HomeComponent
      }
  
  }
  </code></pre>
  <p><code>HomeModule</code> class to provide the <code>home</code> module dependencies</p>
  <pre><code>package com.ibrahim.home
  
  import com.ibrahim.currencyconverter.di.AppModule
  import com.ibrahim.currencyconverter.di.FragmentViewModelModule
  import dagger.Module
  import dagger.Provides
  import dagger.hilt.InstallIn
  import dagger.hilt.android.components.FragmentComponent
  import kotlinx.coroutines.ExperimentalCoroutinesApi
  import retrofit2.Retrofit
  
  @Module(
      includes = [
          FragmentViewModelModule::class,
          AppModule::class
      ]
  )
  @InstallIn(FragmentComponent::class)
  object HomeModule {
  
      @Provides
      fun provideHomeRemoteDataSource(retrofit: Retrofit): IHomeRemoteDataSource {
          return retrofit.create(IHomeRemoteDataSource::class.java)
      }
  
      @ExperimentalCoroutinesApi
      @Provides
      fun provideHomeRepository(repository: HomeRepository): IHomeRepository {
          return repository
      }
  
  }
  </code></pre>
  <p><code>HomeViewModel</code> class</p>
  <pre><code>package com.ibrahim.home
  
  import androidx.lifecycle.SavedStateHandle
  import com.ibrahim.core.CoreViewModel
  import com.ibrahim.core.None
  import com.ibrahim.core.exhaustive
  import dagger.assisted.Assisted
  import dagger.assisted.AssistedInject
  import kotlinx.coroutines.ExperimentalCoroutinesApi
  import kotlinx.coroutines.flow.collect
  import timber.log.Timber
  
  @ExperimentalCoroutinesApi
  class HomeViewModel @AssistedInject constructor(
      private val latestExchangeRateUseCase: LatestExchangeRateUseCase,
      @Assisted private val savedStateHandle: SavedStateHandle
  ) :
      CoreViewModel&lt;HomeIntent, HomeResult, HomeState&gt;(HomeState.Idle) {
  
      override suspend fun handleIntent(intent: HomeIntent) {
          when (intent) {
              is HomeIntent.GetLatestExchangeRate -&gt; {
                  latestExchangeRateUseCase.execute(None()).collect {
                      updateState(it)
                  }
              }
          }.exhaustive
      }
  
      override fun reduce(result: HomeResult): HomeState {
          return when (result) {
              is HomeResult.Loading -&gt; {
                  HomeState.Loading
              }
              is HomeResult.ExchangeRateSuccess -&gt; {
                  HomeState.ExchangeRateSuccess(result.exchangeRates)
              }
              is HomeResult.ExchangeRateFailure -&gt; {
                  HomeState.ExchangeRateFailure(result.failure)
              }
          }.exhaustive
      }
  
      override fun onCleared() {
          Timber.i(&quot;HomeViewModel: onCleared()&quot;)
          super.onCleared()
      }
  
  }
  </code></pre>
  <p>And finally the <code>HomeFragment</code> class</p>
  <pre><code>package com.ibrahim.home
  
  import android.content.Context
  import android.os.Bundle
  import android.view.LayoutInflater
  import android.view.View
  import android.view.ViewGroup
  import android.widget.Toast
  import androidx.fragment.app.Fragment
  import androidx.fragment.app.viewModels
  import androidx.lifecycle.lifecycleScope
  import androidx.navigation.fragment.findNavController
  import androidx.recyclerview.widget.DividerItemDecoration
  import com.ibrahim.core.Failure
  import com.ibrahim.core.exhaustive
  import com.ibrahim.currencyconverter.di.AppDependencies
  import com.ibrahim.currencyconverter.di.DFMSavedStateViewModelFactory
  import com.ibrahim.currencyconverter.exchangerate.ExchangeRateData
  import com.ibrahim.home.databinding.FragmentHomeBinding
  import dagger.hilt.android.EntryPointAccessors
  import kotlinx.coroutines.ExperimentalCoroutinesApi
  import kotlinx.coroutines.flow.collect
  import kotlinx.coroutines.launch
  import timber.log.Timber
  import java.net.SocketTimeoutException
  import javax.inject.Inject
  
  @ExperimentalCoroutinesApi
  class HomeFragment : Fragment() {
  
      private var _binding: FragmentHomeBinding? = null
  
      // This property is only valid between onCreateView and
      // onDestroyView.
      private val binding get() = _binding!!
  
      @Inject
      lateinit var savedStateViewModelFactory: DFMSavedStateViewModelFactory
  
      private val viewModel by viewModels&lt;HomeViewModel&gt; { savedStateViewModelFactory }
  
      private val adapter: ExchangeRatesAdapter by lazy {
          ExchangeRatesAdapter {
              val base =
                  if (viewModel.state.value is HomeState.ExchangeRateSuccess) {
                      (viewModel.state.value as HomeState.ExchangeRateSuccess).exchangeRates.base
                  } else {
                      &quot;&quot;
                  }
              findNavController().navigate(
                  HomeFragmentDirections.actionHomeFragmentToExchangeRateFragment(
                      ExchangeRateData(
                          base = base,
                          target = it.name,
                          rate = it.rate
                      )
                  )
              )
          }
      }
  
      override fun onAttach(context: Context) {
          super.onAttach(context)
          DaggerHomeComponent.builder()
              .fragment(this)
              .appDependencies(
                  EntryPointAccessors.fromApplication(
                      requireContext().applicationContext,
                      AppDependencies::class.java
                  )
              )
              .build()
              .inject(this)
      }
  
      override fun onCreateView(
          inflater: LayoutInflater, container: ViewGroup?,
          savedInstanceState: Bundle?
      ): View {
          // Inflate the layout for this fragment
          _binding = FragmentHomeBinding.inflate(inflater, container, false)
          return binding.root
      }
  
      override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
          super.onViewCreated(view, savedInstanceState)
  
          binding.rvRates.addItemDecoration(
              DividerItemDecoration(
                  requireContext(),
                  DividerItemDecoration.VERTICAL
              )
          )
          binding.rvRates.setHasFixedSize(true)
          binding.rvRates.adapter = adapter
  
          lifecycleScope.launchWhenStarted {
              viewModel.state.collect { state -&gt;
                  render(state)
              }
          }
  
      }
  
      private fun render(state: HomeState) {
          when (state) {
              is HomeState.Idle -&gt; {
                  binding.progressBar.visibility = View.VISIBLE
              }
  
              is HomeState.Loading -&gt; {
                  binding.progressBar.visibility = View.VISIBLE
              }
  
              is HomeState.ExchangeRateSuccess -&gt; {
                  onLatestExchangeRateSuccess(state.exchangeRates)
                  binding.progressBar.visibility = View.GONE
              }
  
              is HomeState.ExchangeRateFailure -&gt; {
                  onLatestExchangeRateFailure(state.failure)
                  binding.progressBar.visibility = View.GONE
              }
          }.exhaustive
      }
  
      private fun onLatestExchangeRateSuccess(exchangeRates: ExchangeRates) {
          Timber.i(&quot;latestExchangeRate: ${exchangeRates.exchangeRates.size}&quot;)
          requireActivity().title = exchangeRates.base
          if (adapter.items.isNullOrEmpty()) {
              adapter.items.addAll(exchangeRates.exchangeRates)
              adapter.notifyDataSetChanged()
          }
      }
  
      private fun onLatestExchangeRateFailure(failure: Failure) {
          Timber.i(&quot;latestExchangeRateFailure: $failure&quot;)
          var errorMessage: String = getString(R.string.something_went_wrong_please_try_again_later)
          when (failure) {
              is Failure.NetworkConnection -&gt; {
                  errorMessage =
                      if (failure.throwable is SocketTimeoutException)
                          getString(R.string.looks_like_the_server_is_taking_too_long_to_respond_please_try_again_later)
                      else
                          getString(R.string.no_internet_connection)
              }
              is Failure.ServerError -&gt; {
                  errorMessage = getString(R.string.no_internet_connection)
              }
              is Failure.FeatureFailure -&gt; {
              }
          }
          Toast.makeText(
              requireContext(),
              errorMessage,
              Toast.LENGTH_SHORT
          ).show()
      }
  
      override fun onActivityCreated(savedInstanceState: Bundle?) {
          super.onActivityCreated(savedInstanceState)
          if (viewModel.state.value !is HomeState.ExchangeRateSuccess) {
              lifecycleScope.launch {
                  viewModel dispatch HomeIntent.GetLatestExchangeRate
              }
          }
      }
  
      override fun onDestroyView() {
          super.onDestroyView()
          Timber.i(&quot;onDestroyView&quot;)
      }
  
      override fun onDetach() {
          super.onDetach()
          Timber.i(&quot;onDetach&quot;)
      }
  
      override fun onDestroy() {
          super.onDestroy()
          Timber.i(&quot;onDestroy&quot;)
      }
  
  }
  </code></pre>
  <p>I'm unable to build the project due to the following error and I don't know what is wrong and how I can fix it. Also, I have checked the <a href="https://developer.android.com/training/dependency-injection/hilt-multi-module" rel="nofollow noreferrer">hilt-multi-module documentation</a></p>
  <pre><code>/home/ibrahim/Android/Projects/CurrencyConverter/home/build/tmp/kapt3/stubs/debug/com/ibrahim/home/HomeComponent.java:8: error: [Dagger/MissingBinding] java.util.Map&lt;java.lang.String,javax.inject.Provider&lt;androidx.hilt.lifecycle.ViewModelAssistedFactory&lt;? extends androidx.lifecycle.ViewModel&gt;&gt;&gt; cannot be provided without an @Provides-annotated method.
  public abstract interface HomeComponent {
                  ^
        java.util.Map&lt;java.lang.String,javax.inject.Provider&lt;androidx.hilt.lifecycle.ViewModelAssistedFactory&lt;? extends androidx.lifecycle.ViewModel&gt;&gt;&gt; is injected at
            com.ibrahim.currencyconverter.di.FragmentViewModelModule.provideSavedStateViewModelFactory(…, viewModelFactories)
        com.ibrahim.currencyconverter.di.DFMSavedStateViewModelFactory is injected at
            com.ibrahim.home.HomeFragment.savedStateViewModelFactory
        com.ibrahim.home.HomeFragment is injected at
            com.ibrahim.home.HomeComponent.inject(com.ibrahim.home.HomeFragment)warning: The following options were not recognized by any processor: '[dagger.hilt.android.internal.disableAndroidSuperclassValidation, kapt.kotlin.generated]'
  </code></pre>