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.
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()
@HiltViewModel
for this purpose, since there was no way to switch the implementation.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
@InstallIn(ViewModelComponent::class)
object MyClassModule{
@Provides
fun provideMyClas(): MyClass = MyClassImp()
}
class MyClassImp : MyClass {
// your magic goes here
}
@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.