Mehrdad Faraji
Mehrdad Faraji

Reputation: 3904

How install view model in fragmentComponent with Hilt injection?

I want scope ViewModel and it's dependencies by Fragment not activity,

interface MarsRepository {
    suspend fun getProperties(): List<Mars>
}



@Module
@InstallIn(FragmentComponent::class)
class MarsRepositoryModule {
    
    @Provides
    fun bindRemoteMarsDataSource(): MarsRepository{
        return RemoteMarsStorageImp()
    }
}



class MarsViewModel @ViewModelInject constructor(private val repository: MarsRepository): ViewModel()



@AndroidEntryPoint
class MarsFragment: Fragment() {
    
    val viewModel by viewModels<MarsViewModel>()
}

But I got this

/app/build/generated/source/kapt/debug/com/Sample/bestcase/DevByteApplication_HiltComponents.java:111: error: [Dagger/MissingBinding] com.Sample.domain.repository.MarsRepository cannot be provided without an @Provides-annotated method.
  public abstract static class SingletonC implements DevByteApplication_GeneratedInjector,
                         ^
      javax.inject.Provider<com.Sample.domain.repository.MarsRepository> is injected at
          com.Sample.bestcase.features.mars.MarsViewModel_AssistedFactory(repository)
      com.Sample.bestcase.features.mars.MarsViewModel_AssistedFactory is injected at
          com.Sample.bestcase.features.mars.MarsViewModel_HiltModule.bind(factory)
      java.util.Map<java.lang.String,javax.inject.Provider<androidx.hilt.lifecycle.ViewModelAssistedFactory<? extends androidx.lifecycle.ViewModel>>> is injected at
          androidx.hilt.lifecycle.ViewModelFactoryModules.ActivityModule.provideFactory(…, viewModelFactories)
      @dagger.hilt.android.internal.lifecycle.DefaultActivityViewModelFactory java.util.Set<androidx.lifecycle.ViewModelProvider.Factory> is requested at
          dagger.hilt.android.internal.lifecycle.DefaultViewModelFactories.ActivityEntryPoint.getActivityViewModelFactory() [com.Sample.bestcase.DevByteApplication_HiltComponents.SingletonC → com.Sample.bestcase.DevByteApplication_HiltComponents.ActivityRetainedC → com.Sample.bestcase.DevByteApplication_HiltComponents.ActivityC]
  The following other entry points also depend on it:
      dagger.hilt.android.internal.lifecycle.DefaultViewModelFactories.FragmentEntryPoint.getFragmentViewModelFactory() [com.Sample.bestcase.DevByteApplication_HiltComponents.SingletonC → com.Sample.bestcase.DevByteApplication_HiltComponents.ActivityRetainedC → com.Sample.bestcase.DevByteApplication_HiltComponents.ActivityC → com.Sample.bestcase.DevByteApplication_HiltComponents.FragmentC]

It just works if installed by ActivityComponent

@Module
@InstallIn(ActivityComponent::class)
class MarsRepositoryModule { 
    @Provides
    fun bindRemoteMarsDataSource(): MarsRepository{
        return RemoteMarsStorageImp()
    }
}

As I track the code It uses DefaultActivityViewModelFactory instead of DefaultFragmentViewModelFactory

 /**
     * Hilt Modules for providing the activity level ViewModelFactory
     */
    @Module
    @InstallIn(ActivityComponent.class)
    public abstract static class ActivityModule {

        @NonNull
        @Multibinds
        abstract Map<String, ViewModelAssistedFactory<? extends ViewModel>> viewModelFactoriesMap();

        @Provides
        @IntoSet
        @NonNull
        @DefaultActivityViewModelFactory
        static ViewModelProvider.Factory provideFactory(
                @NonNull Activity activity,
                @NonNull Application application,
                @NonNull Map<String, Provider<ViewModelAssistedFactory<? extends ViewModel>>>
                        viewModelFactories) {
            // Hilt guarantees concrete activity is a subclass of ComponentActivity.
            SavedStateRegistryOwner owner = (ComponentActivity) activity;
            Bundle defaultArgs = activity.getIntent() != null
                    ? activity.getIntent().getExtras() : null;
            SavedStateViewModelFactory delegate =
                    new SavedStateViewModelFactory(application, owner, defaultArgs);
            return new HiltViewModelFactory(owner, defaultArgs, delegate, viewModelFactories);
        }
    }

    /**
     * Hilt Modules for providing the fragment level ViewModelFactory
     */
    @Module
    @InstallIn(FragmentComponent.class)
    public static final class FragmentModule {

        @Provides
        @IntoSet
        @NonNull
        @DefaultFragmentViewModelFactory
        static ViewModelProvider.Factory provideFactory(
                @NonNull Fragment fragment,
                @NonNull Application application,
                @NonNull Map<String, Provider<ViewModelAssistedFactory<? extends ViewModel>>>
                        viewModelFactories) {
            Bundle defaultArgs = fragment.getArguments();
            SavedStateViewModelFactory delegate =
                    new SavedStateViewModelFactory(application, fragment, defaultArgs);
            return new HiltViewModelFactory(fragment, defaultArgs, delegate, viewModelFactories);
        }

        private FragmentModule() {
        }
    }

Upvotes: 0

Views: 2052

Answers (1)

Andrew
Andrew

Reputation: 4712

The viewmodel is already scoped by its fragment. What the compiler tries to tell you is, that you are trying to inject your repository inside a component, that outlives the fragment.

As it says in the component section here:

Each component is responsible for injecting a different type of Android class. This is shown in the table below: enter image description here

In order to inject a dependency in a viewmodel, it has to be at least ActivityRetainedScoped


Fix

Change FragmentComponent::class with ActivityRetainedComponent::class or SingletonComponent::class

@Module
@InstallIn(SingletonComponent::class) // or at least ActivityRetainedComponent
class MarsRepositoryModule { 
    @Provides
    fun bindRemoteMarsDataSource(): MarsRepository{
        return RemoteMarsStorageImp()
    }
}

Upvotes: 1

Related Questions