Hilt dependency injection error with ActivityContext - android

I am using Hilt. In my ViewModel, I have EventLogger class and a repository class, which are throwing me an error while being built, so build is unsuccessful. The code is as follows,
#HiltViewModel
class ConversionViewModel #Inject constructor(
private val preConversionUseCase : PreConversionUseCase,
private val conversionUseCase: ConversionUseCase,
private val conversionSmsUseCase: ConversionSmsUseCase,
private val currencyRepository: MyBankRepository,
private val logger: EventLogger
) : ViewModel() { ... }
class EvenLogger #Inject constructor(
#ActivityContext val context: Context
){ ... }
class MyBankRepository #Inject constructor(
#ActivityContext private val module: ApiModule
): ApiRequest() {
val api = module.provideRetrofit()
}
I am getting the following error:

I think you can't inject the ActivityContext into an ApiModule. You need to add an Context-type param to the constructor of your MyBankRepository, I suppose:
class MyBankRepository #Inject constructor (
#ActivityContext
val context: Context,
private val module: ApiModule
) : ApiRequest() { ...
But this does unfortunately not work for ViewModels as described here:
https://github.com/google/dagger/issues/2698#issuecomment-862516150
The Repository and the EventLogger are both children of a ViewModel.
So it looks like you have to chance your architecture a little bit. You shouldn't implement too much logic into an ViewModel.

Related

How to inject Repository in ordinary classes using hilt

MyRepository
class MyRepository #Inject constructor(
private val myDao: IMyDao
){
...
}
MyModule
#InstallIn(SingletonComponent::class)
#Module
class MyModule {
#Provides
fun provideMyRepository(MyDao: IMyDao): MyRepository{
return MyRepository(MyDao)
}
}
Use in Worker
class MyWorker(appContext: Context, workerParams: WorkerParameters) :
Worker(appContext, workerParams) {
private val myRepository: MyRepository =
EntryPointAccessors.fromApplication(appContext, MyRepository::class.java)
}
start worker in Application class.
but got error: java.lang.ClassCastException: Cannot cast com.freedom.android.DaggerMyApplication_HiltComponents_SingletonC$SingletonCImpl to com.freedom.android.MyRepository
I read the relevant documentation.But I don't want to use the interface, I just want the class to be provided.
Please tell me what is the problem.
In this case you need to use hilt worker
#HiltWorker class WokerName #AssistedInject constructor(
#Assisted appContext: Context,
#Assisted params: WorkerParameters,
myRepository: MyRepository): CoroutineWorker(appContext, params)
Go through all the links below you will get all the gradel dependency, how to use them etc..
https://developer.android.com/training/dependency-injection/hilt-jetpack#kotlin
https://developer.android.com/reference/androidx/hilt/work/HiltWorker
https://developer.android.com/guide/background/persistent/configuration/custom-configuration
https://developer.android.com/topic/libraries/app-startup

How can I dependency injection Context into ViewModel using Hilt in Android Studio?

Before, I use Code A to pass Context to ViewModel.
Now I hope to use Hilt as dependency injection to pass Context,
I have read the article , and Code B is from the article.
1: Is the Code B correct way to pass Context into ViewModel?
2: In my mind, in order to use Hilt in Android Studio project, I have added such as the Code C in project, do I need to use fun provideApplicationContext() = MyApplication() in Code B?
Code A
class HomeViewModel(private val mApplication: Application, val mRepository: DBRepository) : AndroidViewModel(mApplication) {
...
}
Code B
class MainViewModel #ViewModelInject constructor(
#ApplicationContext private val context: Context,
private val repository: Repository,
#Assisted private val savedStateHandle: SavedStateHandle
) : ViewModel() {
...
}
#Singleton
#Provides
fun provideApplicationContext() = MyApplication()
Code C
#HiltAndroidApp
class MyApplication : Application() {
}
This is how I injected applicationContext in the viewmodel and it worked fine.
Base Application
#HiltAndroidApp
class BaseApplication: Application()
App Module
#Module
#InstallIn(SingletonComponent::class)
object AppModule {
#Singleton
#Provides
fun provideApplication(#ApplicationContext app: Context): BaseApplication{
return app as BaseApplication
}
View Model
#HiltViewModel
class PendingListViewModel
#Inject
constructor(private val application: BaseApplication)
Usage In the ViewModel
AppCompatResources.getDrawable(application.applicationContext, R.drawable.marker_circle)

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.
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()
I don't think there is a way to use #HiltViewModel for this purpose, since there was no way to switch the implementation.
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()
}

android hilt Inject into ViewModel

i am try to inject a module to MyViewModel
here is my Module
#Module
#InstallIn(ViewModelComponent::class)
object EngineModule {
#Provides
fun getEngine(): String = "F35 Engine"
}
and this my viewModel
#HiltViewModel
class MyViewModel #Inject constructor(): ViewModel() {
#Inject lateinit var getEngine: String
fun getEngineNameFromViewModel(): String = getEngineName()
}
and it throws
kotlin.UninitializedPropertyAccessException: lateinit property getEngine
has not been initialized
however if i change ViewModelComponent::class to ActivityComponent::class and inject like this
#AndroidEntryPoint
class MainActivity : ComponentActivity() {
#Inject
lateinit var getEngine: String
it works perfectly
any idea how to inject viewModels?
Also you can just remove #Inject constructor since you are already providing the dependency using dagger module:
#HiltViewModel
class MyViewModel (private val engineName: String): ViewModel() {
fun getEngineNameFromViewModel(): String = engineName
}
So, Basically you can either provide the dependency using dagger module or constructor injection.
Since required dependency is going to be injected in the ViewModel's constructor, you just need to modify your code in the following way to make it work:
#HiltViewModel
class MyViewModel #Inject constructor(private val engineName: String): ViewModel() {
fun getEngineNameFromViewModel(): String = engineName
}

Application dependency to ViewModel with HILT

I was wondering how can I pass application dependency to ViewModel using Hilt?
I was trying with AndroidViewModel, but I couldn't make it. Can someone help me? Some short sample could will mean a lot to me.
This is my ViewModel:
class MainViewModel #ViewModelInject constructor(
private val application: Application,
private val repository: Repository,
#Assisted private val savedStateHandle: SavedStateHandle
) : ViewModel() {
This is my hilt module
#Module
#InstallIn(ApplicationComponent::class)
object DatabaseModule {
#Singleton
#Provides
fun provideDatabase(
#ApplicationContext context: Context
) = Room.databaseBuilder(
context,
MyDatabase::class.java,
"my_database"
).build()
#Singleton
#Provides
fun provideDao(database: MyDatabase) = database.myDao()
#Singleton
#Provides
fun provideRepository(myDao: MyDao) = Repository(myDao)
#Singleton
#Provides
fun provideApplicationContext() = MyApplication()
}
Everything else is fine, and I got the error message:
Caused by: java.lang.RuntimeException: Cannot create an instance of
class com.example.example.viewmodel.MainViewModel
Caused by: java.lang.InstantiationException:
java.lang.Class<com.example.example.viewmodel.MainViewModel> has
no zero argument constructor
You can see full source https://github.com/Kotlin-Android-Open-Source/MVI-Coroutines-Flow/tree/dagger_hilt
Repository:
#Singleton
class UserRepositoryImpl #Inject constructor(
private val userApiService: UserApiService,
private val dispatchers: CoroutineDispatchers,
...
) : UserRepository { ... }
Usecases:
class AddUserUseCase #Inject constructor(private val userRepository: UserRepository) {
suspend operator fun invoke(user: User) = userRepository.add(user)
}
class RemoveUserUseCase #Inject constructor(private val userRepository: UserRepository) {
suspend operator fun invoke(user: User) = userRepository.remove(user)
}
class RefreshGetUsersUseCase #Inject constructor(private val userRepository: UserRepository) {
suspend operator fun invoke() = userRepository.refresh()
}
...
ViewModel:
class MainVM #ViewModelInject constructor(
private val getUsersUseCase: GetUsersUseCase,
private val refreshGetUsers: RefreshGetUsersUseCase,
private val removeUser: RemoveUserUseCase,
) : ViewModel() { ... }
Activity:
#AndroidEntryPoint
class MainActivity : AppCompatActivity(), View {
private val mainVM by viewModels<MainVM>()
...
}
Edited:
To inject application context:
First, remove this definition, because Hilt already provides application context:
#Singleton
#Provides
fun provideApplicationContext() = MyApplication()
Second, Use #ApplicationContext annotation on your context parameter.
class MainViewModel #ViewModelInject constructor(
#ApplicationContext private val context: Context,
private val repository: Repository,
#Assisted private val savedStateHandle: SavedStateHandle
) : ViewModel() {
Use #ApplicationContext Context context as a parameter in the constructor.

Categories

Resources