I got an error
public abstract static class SingletonC implements FragmentGetContextFix.FragmentGetContextFixEntryPoint,
^
kotlin.jvm.functions.Function1<? super pl.beskidmedia.bm.viewModel.repozytory.model.Token,kotlin.Unit> is injected at
pl.beskidmedia.bm.domainlayer.viewmodel.tv.TvViewModel(fetchHlsUseCase, �)
pl.beskidmedia.bm.domainlayer.viewmodel.tv.TvViewModel is injected at
pl.beskidmedia.bm.domainlayer.viewmodel.tv.TvViewModel_HiltModules.BindsModule.binds(vm)
#dagger.hilt.android.internal.lifecycle.HiltViewModelMap java.util.Map<java.lang.String,javax.inject.Provider<androidx.lifecycle.ViewModel>> is requested at
dagger.hilt.android.internal.lifecycle.HiltViewModelFactory.ViewModelFactoriesEntryPoint.getHiltViewModelMap() [pl.beskidmedia.bm.MainApplication_HiltComponents.SingletonC ? pl.beskidmedia.bm.MainApplication_HiltComponents.ActivityRetainedC ? pl.beskidmedia.bm.MainApplication_HiltComponents.ViewModelC]
when i tried to provide my use-case to view model, here are my functions that should create use case invoke() instance that i want to use later in my view model
fun getHls(
tvRepository: TvRepository
): Flow<List<Hls>> {
return tvRepository.getHls()
}
typealias GetHlsUseCase = () -> Flow<List<Hls>>
fun fetchHls(
tvRepository: TvRepository,
token: Token
) {
return tvRepository.fetchHls(token.token)
}
typealias FetchHlsUseCase = (Token) -> Unit
and here are my modules
#Module
#InstallIn(ViewModelComponent::class)
class ProvideFetchHlsUseCase {
#Provides
fun provideFetchHlsUseCase(
tvRepository: TvRepository
): FetchHlsUseCase = { fetchHls(tvRepository, it) }
}
#Module
#InstallIn(ViewModelComponent::class)
class ProvideGetHlsUseCase {
#Provides
fun provideGetHlsUseCase(
tvRepository: TvRepository
): GetHlsUseCase = { getHls(tvRepository) }
}
and finally my view model
#HiltViewModel
class TvViewModel #Inject constructor(
private val fetchHlsUseCase: FetchHlsUseCase,
private val getHlsUseCase: GetHlsUseCase
) : BaseViewModel() {
val hls = getHlsUseCase().asLiveData(coroutineExceptionHandler)
}
I can't change my provide to binding (this is what i saw in different questions here) or at least I don't know how to do it. When I was implementing this into my project I was inspired by https://medium.com/swlh/functional-use-cases-f896f92e768f this article, but author show only how to implement view model (no interface or bindings). From what i understands dagger should use designated provide methods that I implemented in modules but for some reason it doesn't happen.
Ok so this was a tricky one cuz this problem is actually well known but it is hard to google if you don't know how to name it, this was resolved here
In short all I needed to do was insert #JvmSuppressWildcards behind my typealias "classes" in ViewModel at the insertion point like so
#HiltViewModel
class TvViewModel #Inject constructor(
private val fetchHlsUseCase: #JvmSuppressWildcards FetchHlsUseCase,
private val getHlsUseCase: #JvmSuppressWildcards GetHlsUseCase
) : BaseViewModel() {
putting this annotation behind private keyword did nothing that why i had problems with this :D
Related
I have this interface
interface ShowsScheduleRepository {
suspend fun getShowsSchedule(medium: String, timeSlot: String): ShowsSchedule
}
which is provided in this class
class GetShowsScheduleUseCase #Inject constructor(
private val repository: ShowsScheduleRepository
)
by
#Provides
#Singleton
fun provideShowsScheduleRepository(api: TDRetrofitApi): ShowsScheduleRepository {
return ShowsScheduleRepoImpl(api)
}
and then this class is injected in a viewModel
#HiltViewModel
class HomeFragmentViewModel #Inject constructor(
private val getShowsSchedule: GetShowsScheduleUseCase
) : ViewModel() {
And when I am trying to build I get the error that it cannot provide a ShowsScheduleRepository without an #Provides-annotated method
Note to self: Put the AppModule file in the same Android Module where the HiltEntryPoint is
The provideShowsScheduleRepository method should be placed in a module class (by saying that I mean a class annotated with #Module). Are you sure that this module is installed in the correct scope? If not, try adding #InstallIn(SingletonComponent::class). (You can replace SingletonComponent with any other component that is described here based on your needs)
I have three class needing to share a dependency. The latter is initialisated by one of them.The SettingsViewModel contains the data to initialize the dependency and it need to be deleted at the end of the activity. NetworkViewModel and TimeViewModel use it as an interface since the dependancy is an interface with the logic to handle Bluetooth.
SettingsViewModel -->(initialize) SingletonDependency.
NetworkViewModel --> (use).
TimeViewModel --> (use).
How can I make Hilt (or manual) injection to use the same interface? If I understand well I can't use singleton here since I need to iniatilize the dependency when the activity start.
If we consider that your class name is SomeClass you can provide a live data of this class like this:
#Module
#InstallIn(SingletonComponent::class)
object SingeltonModule {
#Provides
#Singleton
fun provideSomeClassLiveData(): MutableLiveData<SomeClass> {
return MutableLiveData<SomeClass>()
}
}
in your SettingsViewModel do this:
#HiltViewModel
class SettingsViewModel #Inject constructor(
val SomeClassLiveData: MutableLiveData<SomeClass>
) : ViewModel() {
init{
someClassLiveData.value = SomeClass()
}
}
and in other view models you can inject this to contractors and observe it:
#HiltViewModel
class NetworkViewModel #Inject constructor(
val SomeClassLiveData: MutableLiveData<SomeClass>
) : ViewModel() {
init{
someClassLiveData.observeForEver{
//do what you want with value
}
}
}
I'm trying to wrap my head around Hilt and the way it deals with ViewModels.
I would like my fragments to depend on abstract view models, so I can easily mock them during UI tests. Ex:
#AndroidEntryPoint
class MainFragment : Fragment() {
private val vm : AbsViewModel by viewModels()
/*
...
*/
}
#HiltViewModel
class MainViewModel(private val dependency: DependencyInterface) : AbsViewModel()
abstract class AbsViewModel : ViewModel()
Is there a way to configure by viewModels() so that it can map concrete implementations to abstract view models? Or pass a custom factory producer to viewModels() that can map concrete view models instances to abstract classes?
The exact question is also available here, but it is quite old considering hilt was still in alpha then: https://github.com/google/dagger/issues/1972
However, the solution provided there is not very desirable since it uses a string that points to the path of the concrete view model. I think this will not survive obfuscation or moving files and it can quickly become a nightmare to maintain. The answer also suggests injecting a concrete view model into the fragment during tests with all the view model's dependencies mocked, thus gaining the ability to control what happens in the test. This automatically makes my UI test depend on the implementation of said view model, which I would very much like to avoid.
Not being able to use abstract view models in my fragments makes me think I'm breaking the D in SOLID principles, which is something that I would also like to avoid.
Not the cleanest solution, but here's what I managed to do.
First create a ViewModelClassesMapper to help map an abstract class to a concrete one. I'm using a custom AbsViewModel in my case, but this can be swapped out for the regular ViewModel. Then create a custom view model provider that depends on the above mapper.
class VMClassMapper #Inject constructor (private val vmClassesMap: MutableMap<Class<out AbsViewModel>, Provider<KClass<out AbsViewModel>>>) : VMClassMapperInterface {
#Suppress("TYPE_INFERENCE_ONLY_INPUT_TYPES_WARNING")
override fun getConcreteVMClass(vmClass: Class<out AbsViewModel>): KClass<out AbsViewModel> {
return vmClassesMap[vmClass]?.get() ?: throw Exception("Concrete implementation for ${vmClass.canonicalName} not found! Provide one by using the #ViewModelKey")
}
}
interface VMClassMapperInterface {
fun getConcreteVMClass(vmClass: Class<out AbsViewModel>) : KClass<out AbsViewModel>
}
interface VMDependant<VM : AbsViewModel> : ViewModelStoreOwner {
fun getVMClass() : KClass<VM>
}
class VMProvider #Inject constructor(private val vmMapper: VMClassMapperInterface) : VMProviderInterface {
#Suppress("UNCHECKED_CAST")
override fun <VM : AbsViewModel> provideVM(dependant: VMDependant<VM>): VM {
val concreteClass = vmMapper.getConcreteVMClass(dependant.getVMClass().java)
return ViewModelProvider(dependant).get(concreteClass.java) as VM
}
}
interface VMProviderInterface {
fun <VM :AbsViewModel> provideVM(dependant: VMDependant<VM>) : VM
}
#Module
#InstallIn(SingletonComponent::class)
abstract class ViewModelProviderModule {
#Binds
abstract fun bindViewModelClassesMapper(mapper: VMClassMapper) : VMClassMapperInterface
#Binds
#Singleton
abstract fun bindVMProvider(provider: VMProvider) : VMProviderInterface
}
Then, map your concrete classes using the custom ViewModelKey annotation.
#Target(
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER
)
#kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
#MapKey
annotation class ViewModelKey(val value: KClass<out AbsViewModel>)
#Module
#InstallIn(SingletonComponent::class)
abstract class ViewModelsDI {
companion object {
#Provides
#IntoMap
#ViewModelKey(MainContracts.VM::class)
fun provideConcreteClassForMainVM() : KClass<out AbsViewModel> = MainViewModel::class
#Provides
#IntoMap
#ViewModelKey(SecondContracts.VM::class)
fun provideConcreteClassForSecondVM() : KClass<out AbsViewModel> = SecondViewModel::class
}
}
interface MainContracts {
abstract class VM : AbsViewModel() {
abstract val textLiveData : LiveData<String>
abstract fun onUpdateTextClicked()
abstract fun onPerformActionClicked()
}
}
interface SecondContracts {
abstract class VM : AbsViewModel()
}
Finally, your fragment using the abstract view model looks like this:
#AndroidEntryPoint
class MainFragment : Fragment(), VMDependant<MainContracts.VM> {
#Inject lateinit var vmProvider: VMProviderInterface
protected lateinit var vm : MainContracts.VM
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
vm = vmProvider.provideVM(this)
}
override fun getVMClass(): KClass<MainContracts.VM> = MainContracts.VM::class
}
It's a long way to go, but after you have the initial setup is completed, all you need to do for individual fragments is to make them implement VMDependant and provide a concrete class for YourAbsViewModel in Hilt using the #ViewModelKey.
In tests, vmProvider can then be easily mocked and forced to do your bidding.
i follow this tutorial Room Dependency Injection - MVVM To-Do List App with Flow and Architecture Components #4
Now I want to convert, a hole app that I have with Room, to this Clean Architecture.
In the tutorial Florian uses DI, to inject TaskDao into TaskViewModel, but I have a Repositories clases.
So I get to a point where the app is build without errors.
and this is my Repository:
class AnioRepository constructor(
private val agrotrackerApi: AgrotrackerApi
) {
val TAG = "AnioRepository"
//val anioDao: AnioDao
fun downloadAnios(): AniosResponse? {
GlobalScope.launch(Dispatchers.IO) {
val result = agrotrackerApi.getAnios()
if (result.isSuccessful) {
for (anio in result.body()!!){
Log.d(TAG, anio.toString())
}
}
}
return null
}
fun getAnios() {
//anioDao.getListAnios()
}
}
and this is my RepositoryModule:
#Module
#InstallIn(ApplicationComponent::class)
object RepositoryModule {
#Singleton
#Provides
fun providesAnioRepository( agrotrackerApi: AgrotrackerApi) : AnioRepository {
return AnioRepository(agrotrackerApi)
}
}
So I'm trying to add Dao class to the Repository Class, like this:
class AnioRepository constructor(
private val anioDao: AnioDao,
private val agrotrackerApi: AgrotrackerApi
) {
val TAG = "AnioRepository"
...
and then, change RepositoryModule, to match constructor...
...
fun providesAnioRepository( anioDao: AnioDao, agrotrackerApi: AgrotrackerApi) : AnioRepository
= AnioRepository(anioDao, agrotrackerApi)
...
but when I press Ctrl-F9, i get this error:
public abstract class ATMDatabase extends androidx.room.RoomDatabase {
^C:\pryectos\AndroidStudioProjects\ATMobileXKt\app\build\generated\source\kapt\debug\com\cse\atm\ATMApplication_HiltComponents.java:155:
error: [Dagger/MissingBinding] #com.cse.atm.di.ApplicationScope
kotlinx.coroutines.CoroutineScope cannot be provided without an
#Provides-annotated method. public abstract static class SingletonC
implements ATMApplication_GeneratedInjector,
^
#com.cse.atm.di.ApplicationScope kotlinx.coroutines.CoroutineScope is injected at
com.cse.atm.database.ATMDatabase.Callback(�, applicationScope)
com.cse.atm.database.ATMDatabase.Callback is injected at
com.cse.atm.di.AppModule.providesDatabase(�, callback)
com.cse.atm.database.ATMDatabase is injected at
com.cse.atm.di.AppModule.providesAnioDao(db)
com.cse.atm.database.AnioDao is injected at
com.cse.atm.di.RepositoryModule.providesAnioRepository(anioDao, �)
javax.inject.Provider<com.cse.atm.data.AnioRepository> is injected at
What means this error? What I'm missing ?
#ApplicationScope annotation is in another AppModule.kt, I dont' where is the problem.
Any help wil be appreciated!!
Best Regards
Your ATMDatabase.Callback is requesting a CoroutineScope with custom qualifier #ApplicationScope, but you are not providing such a CoroutineScope in any of your modules.
To provide a coroutine scope, the tutorial you linked adds this code around the 28-minute mark:
#Provides
#Singleton
fun provideApplicationScope() = CoroutineScope(SupervisorJob())
Since you are using the #ApplicationScope qualifier when requesting a coroutine scope, you will also need to add the qualifier to this #Provides method:
#Provides
#Singleton
#ApplicationScope
fun provideApplicationScope() = CoroutineScope(SupervisorJob())
We're using Dagger2 in our application. I am trying to do a room database and I am writing the repository code, but I would like to inject application context and the DAO for the class.
I have a feeling that you can only do Dagger injection in Fragments, Activities, Services, Applications, etc.
Here's what I have:
class DownloadsDataRepositoryImpl : IDownloadsDataRepository, HasAndroidInjector {
#Inject
lateinit var androidInjector : DispatchingAndroidInjector<Any>
#Inject
lateinit var downloadsDao: DownloadsDao
override fun androidInjector(): AndroidInjector<Any> = androidInjector
init {
androidInjector()
}
}
But I'm sure it's not going to work. Is there a way to do it?
As stated, dagger-android is just a tool to help injecting specific framework classes that you can't have control on it's creation.
The proper approach is to use simple construction injection.
To be more direct on how you should expose it on your #Component, I would need more code, specifically on what you have on your activity/fragment, but here is a crude example (that I did not tested, if there are minor errors, you can fix them following the compiler error messages):
First, you will have some object that exposes your DAO. Probably it's room?
#Entity(tableName = "download_table")
data class DownloadEntity(
#PrimaryKey
val key: String
)
#Dao
interface DownloadsDao {
#Query("SELECT * FROM download_table")
fun load(): List<DownloadEntity>
}
#Database(
entities = [DownloadEntity::class], version = 1
)
abstract class DownloadRoomDatabase : RoomDatabase() {
abstract val downloadsDao: DownloadsDao
}
Now we will create a crude repository that is build with #Inject annotation. Dagger will take care of building this object for us. Notice that I am not using dagger-android for it:
interface IDownloadsDataRepository
class DownloadsDataRepositoryImpl #Inject constructor(
val downloadsDao: DownloadsDao
) : IDownloadsDataRepository
How to expose it to your activity/fragment/service requires more details on your implementation. For example, if it's inside a ViewModel or a Presenter that is annotated with #Inject or you are accessing directly on your activity will result in different implementations. Without more details, I will suppose that you are accessing the repository directly on your activity:
class DownloadActivity : FragmentActivity() {
#Inject
lateinit val repo: IDownloadsDataRepository
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
DaggerDownloadComponent.factory().create(this).inject(this)
}
}
Now we need to instruct Dagger on how to:
Bind your concrete DownloadsDataRepositoryImpl to the IDownloadsDataRepository interface that the activity requires
How to provide the dependencies to build DownloadsDataRepositoryImpl
For this we will need a module:
#Module
abstract class RepositoryModule {
//We will bind our actual implementation to the IDownloadsDataRepository
#Binds
abstract fun bindRepo(repo: DownloadsDataRepositoryImpl): IDownloadsDataRepository
#Module
companion object {
//We need the database to get access to the DAO
#Provides
#JvmStatic
fun provideDataBase(context: Context): DownloadRoomDatabase =
Room.databaseBuilder(
context,
DownloadRoomDatabase::class.java,
"download_database.db"
).build()
//With the database, we can provide the DAO:
#Provides
#JvmStatic
fun provideDao(db: DownloadRoomDatabase): DownloadsDao = db.downloadsDao
}
}
With this, we can finish the last part of our puzzle, creating the #Component:
#Component(
modules = [
RepositoryModule::class
]
)
interface DownloadComponent {
fun inject(activity: DownloadActivity)
#Component.Factory
interface Factory {
fun create(context: Context): DownloadComponent
}
}
Notice that I did not use any dagger-android code, I don't think it's useful and causes more confusion than necessary. Stick with basic dagger2 constructs and you are fine. You can implement 99.9% of your app only understanding how those constructs works:
#Module, #Component and #Subcomponent
Edit: As stated in the comments, probably you will need to properly manage the scope of your repository, specially the DB creation if you are actually using Room.
Not sure how you implemented dagger, but here is an example how you can provide context to non activity class.
Suppose you have AppModule class, so there you can add provideContext() method:
#Module
class AppModule(app: App) {
private var application: Application = app
#Provides
fun provideContext(): Context {
return application
}
}
and here is non activity class written in Kotlin:
class Utils #inject constructor(private val context: Context) {
..
}
And that's it, just rebuild j
I have a feeling that you can only do Dagger injection in Fragments, Activities, Services, Applications, etc.
You were correct to assume that before Dagger-Android 2.20, but not after 2.20+.
Now you can create a #ContributesAndroidInjector for any class, which will generate an AndroidInjector<T> for that T for which you added #ContributesAndroidInjector.
This means that there is a multi-binding that allows you to get an AndroidInjector<T> for a T, and this is what HasAndroidInjector does for you.
So the following worked for me in a different scenario (for member-injecting Workers in work-manager, instead of creating a multi-binding and a factory):
#Keep
class SyncWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
init {
val injector = context.applicationContext as HasAndroidInjector
injector.androidInjector().inject(this)
}
#Inject
lateinit var apiService: ApiService
and
#ContributesAndroidInjector
abstract fun syncWorker(): SyncWorker
HOWEVER in your particular case, none of this is required.
Dagger-Android is for member-injecting classes using an auto-generated subcomponent, that you typically need only if your injected type is inside a different module, and therefore you can't directly add fun inject(T t) into your AppComponent, OR you don't see your AppComponent.
In your case, simple constructor injection is enough, as you own your own class.
#Singleton
class DownloadsDataRepositoryImpl #Inject constructor(
private val downloadsDao: DownloadsDao
): IDownloadsDataRepository {}
Which you can bind via a module
#Module
abstract class DownloadsModule {
#Binds
abstract fun dataRepository(impl: DownloadsDataRepositoryImpl): IDownloadsDataRepository
}
And otherwise you just create your component instance inside Application.onCreate()
#Component(modules = [DownloadsModule::class])
#Singleton
interface AppComponent {
fun dataRepository(): DownloadsDataRepository
#Component.Factory
interface Factory {
fun create(#BindsInstance appContext: Context): AppComponent
}
}
And
class CustomApplication: Application() {
lateinit var component: AppComponent
private set
override fun onCreate() {
super.onCreate()
component = DaggerAppComponent.factory().create(this)
}
}
Then you can get it as
val component = (context.applicationContext as CustomApplication).component
Though technically you may as well create an extension function
val Context.appComponent: AppComponent
get() = (applicationContext as CustomApplication).component
val component = context.appComponent