I am trying to inject abstract class into Fragment etc. in order to inject Fake object without mocking ViewModel with mockito etc. when doing Fragment test.
abstract class MainViewModel : ViewModel() {
abstract fun onClick()
}
class MainViewModelImpl @ViewModelInject constructor() : MainViewModel() {
override fun onClick() {
TODO("Not yet implemented")
}
}
@InstallIn(ActivityRetainedComponent::class)
@Module
interface Module {
@Binds
fun provideMainViewModel(viewModel: MainViewModelImpl): MainViewModel
}
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
val viewModel by viewModels<MainViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
println(viewModel)
}
}
Currently doing this causes a crash.
2020-07-07 19:13:42.284 23496-23496/com.github.takahirom.hilt E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.github.takahirom.hilt, PID: 23496
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.github.takahirom.hilt/com.github.takahirom.hilt.MainActivity}: java.lang.RuntimeException: Cannot create an instance of class com.github.takahirom.hilt.MainViewModel
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3340)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3484)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2044)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7476)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:549)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:939)
Caused by: java.lang.RuntimeException: Cannot create an instance of class com.github.takahirom.hilt.MainViewModel
at androidx.lifecycle.ViewModelProvider$NewInstanceFactory.create(ViewModelProvider.java:221)
at androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.java:278)
at androidx.lifecycle.SavedStateViewModelFactory.create(SavedStateViewModelFactory.java:106)
at androidx.hilt.lifecycle.HiltViewModelFactory.create(HiltViewModelFactory.java:69)
at androidx.lifecycle.AbstractSavedStateViewModelFactory.create(AbstractSavedStateViewModelFactory.java:69)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:185)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:150)
at androidx.lifecycle.ViewModelLazy.getValue(ViewModelProvider.kt:54)
at androidx.lifecycle.ViewModelLazy.getValue(ViewModelProvider.kt:41)
at com.github.takahirom.hilt.MainActivity.getViewModel(Unknown Source:2)
at com.github.takahirom.hilt.MainActivity.onCreate(MainActivity.kt:37)
at android.app.Activity.performCreate(Activity.java:7989)
at android.app.Activity.performCreate(Activity.java:7978)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1309)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3315)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3484)聽
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)聽
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)聽
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)聽
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2044)聽
at android.os.Handler.dispatchMessage(Handler.java:106)聽
at android.os.Looper.loop(Looper.java:223)聽
at android.app.ActivityThread.main(ActivityThread.java:7476)聽
at java.lang.reflect.Method.invoke(Native Method)聽
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:549)聽
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:939)聽
Caused by: java.lang.InstantiationException: java.lang.Class<com.github.takahirom.hilt.MainViewModel> cannot be instantiated
at java.lang.Class.newInstance(Native Method)
at androidx.lifecycle.ViewModelProvider$NewInstanceFactory.create(ViewModelProvider.java:219)
at androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.java:278)聽
at androidx.lifecycle.SavedStateViewModelFactory.create(SavedStateViewModelFactory.java:106)聽
at androidx.hilt.lifecycle.HiltViewModelFactory.create(HiltViewModelFactory.java:69)聽
at androidx.lifecycle.AbstractSavedStateViewModelFactory.create(AbstractSavedStateViewModelFactory.java:69)聽
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:185)聽
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:150)聽
at androidx.lifecycle.ViewModelLazy.getValue(ViewModelProvider.kt:54)聽
at androidx.lifecycle.ViewModelLazy.getValue(ViewModelProvider.kt:41)聽
at com.github.takahirom.hilt.MainActivity.getViewModel(Unknown Source:2)聽
at com.github.takahirom.hilt.MainActivity.onCreate(MainActivity.kt:37)聽
at android.app.Activity.performCreate(Activity.java:7989)聽
at android.app.Activity.performCreate(Activity.java:7978)聽
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1309)聽
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3315)聽
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3484)聽
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)聽
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)聽
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)聽
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2044)聽
at android.os.Handler.dispatchMessage(Handler.java:106)聽
at android.os.Looper.loop(Looper.java:223)聽
at android.app.ActivityThread.main(ActivityThread.java:7476)
This is not easily supported right now, but you can make it work if you change your binds module to:
@InstallIn(ActivityRetainedComponent::class)
@Module
interface Module {
@Binds
@IntoMap
@StringKey("com.github.takahirom.hilt.MainViewModel")
fun bind(factory: MainViewModelImpl_AssistedFactory): ViewModelAssistedFactory<out ViewModel>
}
while also enabling KAPT corrected error types:
// app's build.gradle
dependencies {
...
}
kapt {
correctErrorTypes = true
}
Hilt ViewModel extension works by declaring modules that bind assisted factories to a map and not by binding concrete ViewModels. Therefore, what you want to do is bind the assisted factory of the concrete ViewModel using the key of the abstract ViewModel so that when HiltViewModelFactory looks up the factory based on class key it uses the assisted factory for the concrete ViewModel. This is suuuper obscure and hence why I mean not 'easily' available.
However, if you can expand on the test case your are trying to write that could help us provide some guidance, I'm not sure if you are trying to mock/fake the ViewModel itself for tests, but Hilt testing APIs should allow you to replace dependencies in the ViewModel so you can write a test with the Fragment and the ViewModel.
Hello Daniel
Thank you for thinking.
The following are shared as personal use cases.
Actually I'm trying to test how it's displayed by a ViewModel property (UiModel) by comparing screenshots etc. So I want to mock the ViewModel.
I will try to find another approach.
class MainViewModel @ViewModelInject constructor() : ViewModel() {
class UiModel(
val isShowProgress: Boolean
)
private val mutableUiModel = MutableLiveData(UiModel(true))
val uiModel: LiveData<UiModel>
get() = mutableUiModel
fun onClick() {
TODO("Not yet implemented")
}
}
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
val viewModel by viewModels<MainViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel.uiModel.observe(this) { uiModel ->
// binding.progressBar.isVisible = uiModel.isShowProgress
}
}
}
Most helpful comment
This is not easily supported right now, but you can make it work if you change your binds module to:
while also enabling KAPT corrected error types:
Hilt ViewModel extension works by declaring modules that bind assisted factories to a map and not by binding concrete ViewModels. Therefore, what you want to do is bind the assisted factory of the concrete ViewModel using the key of the abstract ViewModel so that when
HiltViewModelFactorylooks up the factory based on class key it uses the assisted factory for the concrete ViewModel. This is suuuper obscure and hence why I mean not 'easily' available.However, if you can expand on the test case your are trying to write that could help us provide some guidance, I'm not sure if you are trying to mock/fake the ViewModel itself for tests, but Hilt testing APIs should allow you to replace dependencies in the ViewModel so you can write a test with the Fragment and the ViewModel.