简体   繁体   中英

Hilt - How to inject ViewModel interface?

Based on the Hilt tutorial, ViewModels needs to be inject the following way:

@HiltViewModel
class ExampleViewModel @Inject constructor(
  private val savedStateHandle: SavedStateHandle,
  private val repository: ExampleRepository
) : ViewModel() {
  ...
}

However, in my case, I want to use an interface:

interface ExampleViewModel()

@HiltViewModel
class ExampleViewModelImp @Inject constructor(
  private val savedStateHandle: SavedStateHandle,
  private val repository: ExampleRepository
) : ExampleViewModel, ViewModel() {
  ...
}

Then I want to inject it via the interface

@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {
  private val exampleViewModel: ExampleViewModel by viewModels()
  ...
}

How to make this work?

viewModels requires child of ViewModel class

val viewModel: ExampleViewModel by viewModels<ExampleViewModelImp>()

Had a similar problem where I wanted to Inject the ViewModel via interface, primarily because to switch it with a fake implementation while testing. We are migrating from Dagger Android to Hilt, and we had UI tests that used fake view models. Adding my findings here so that it could help someone whose facing a similar problem.

  1. Both by viewModels() and ViewModelProviders.of(...) expects a type that extends ViewModel() . So interface won't be possible, but we can still use an abstract class that extends ViewModel()
  2. I don't think there is a way to use @HiltViewModel for this purpose, since there was no way to switch the implementation.
  3. So instead, try to inject the ViewModelFactory in the Fragment . You can switch the factory during testing and thereby switch the ViewModel.
@AndroidEntryPoint
class ListFragment : Fragment() {
    
    @ListFragmentQualifier
    @Inject
    lateinit var factory: AbstractSavedStateViewModelFactory

    private val viewModel: ListViewModel by viewModels(
        factoryProducer = { factory }
    )
}
abstract class ListViewModel : ViewModel() {
    abstract fun load()
    abstract val title: LiveData<String>
}

class ListViewModelImpl(
    private val savedStateHandle: SavedStateHandle
) : ListViewModel() {
    override val title: MutableLiveData<String> = MutableLiveData()
    override fun load() {
        title.value = "Actual Implementation"
    }
}

class ListViewModelFactory(
    owner: SavedStateRegistryOwner,
    args: Bundle? = null
) : AbstractSavedStateViewModelFactory(owner, args) {
    override fun <T : ViewModel?> create(
        key: String,
        modelClass: Class<T>,
        handle: SavedStateHandle
    ): T {
        return ListViewModelImpl(handle) as T
    }
}
@Module
@InstallIn(FragmentComponent::class)
object ListDI {

    @ListFragmentQualifier
    @Provides
    fun provideFactory(fragment: Fragment): AbstractSavedStateViewModelFactory {
        return ListViewModelFactory(fragment, fragment.arguments)
    }
}

@Qualifier
annotation class ListFragmentQualifier

Here, ListViewModel is the abstract class and ListViewModelImpl is the actual implementation. You can switch the ListDI module while testing using TestInstallIn . For more information on this, and a working project refer to this article

Found a solution using HiltViewModel as a proxy to the actual class I wish to inject. It is simple and works like a charm;)

Module

@Module
@InstallIn(ViewModelComponent::class)
object MyClassModule{
    @Provides
    fun provideMyClas(): MyClass = MyClassImp()
}

class MyClassImp : MyClass {
    // your magic goes here
}

Fragment

@HiltViewModel
class Proxy @Inject constructor(val ref: MyClass) : ViewModel()

@AndroidEntryPoint
class MyFragment : Fragment() {
   private val myClass by lazy {
        val viewModel by viewModels<Proxy>()
        viewModel.ref
    }
}

Now you got myClass of the type MyClass interface bounded to viewModels<Proxy>() lifeCycle

It's so simple to inject an interface, you pass an interface but the injection injects an Impl.

@InstallIn(ViewModelComponent::class)
@Module
class DIModule {

@Provides
fun providesRepository(): YourRepository = YourRepositoryImpl()

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM