The goal is to inject a class with an optional constructor parameter.
If using #Inject the class will be created without the optional parameter, while if I want to pass the optional parameter I can use a factory #AssistedFactory.
I am trying to achieve it like this:
class ClassName #AssistedInject constructor(
private val database: FirebaseFirestore,
private val functions: FirebaseFunctions,
#Assisted private val division: String?
) {
#Inject constructor(
database: FirebaseFirestore,
functions: FirebaseFunctions
) : this(database, functions, null)
}
But it throws an error when I am injecting ClassName without using a factory
error: Dagger does not support injecting #AssistedInject type, ClassName. Did you mean to inject its assisted factory type instead?
How can I achieve the goal above? Any help is much appreciated
That's not possible, #AssistedInject annotation is processed in a way such that the target injecting class must match the argument number and type in the factory.
However you can remove the #Inject and create provider function that will receive the assisted factory as dependecy
#Provides
fun providesClassName(classNameFactory : ClassName.Factory) : ClassName{
return classNameFactory.create(null)
}
Elsewhere you can just use assisted factory as usual:
#Inject classNameAssistedFactory : ClassName.Factory
override fun onCreate(...){
super.onCreate(...)
classNameAssistedFactory.create("divisionA")
}
Related
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
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)
Hello I'm building the app with hilt dependecy injector, but when I try to build the app i had error from the title. Here's my code:
Flow class:
#Entity(tableName = "flows_table")
data class Flow #Inject constructor(#PrimaryKey(autoGenerate = true) val id:Int, val name:String, val duration:Int, val actions:ArrayList<Action>)
Action class:
data class Action #Inject constructor(
val type: String,
val color: String?,
val brightness: Int?,
val duration: Float
)
My application:
#HiltAndroidApp
class YeebumApplication : Application() {
//get bulbs repository
private val bulbsDatabase by lazy { BulbsDatabase.getInstance(this)}
val bulbsRepository by lazy { BulbsRepository(bulbsDatabase!!.bulbsDao()) }
//get flows repository
private val flowsDatabase by lazy { FlowsDatabase.getInstance(this) }
val flowsRepository by lazy { FlowsRepository(flowsDatabase!!.flowsDao())}
}
My Fragment:
#AndroidEntryPoint
class ActionDetailsFragment : Fragment() {
#Inject
lateinit var flow: Flow
With the activity everything works perfectly, but when I try to inject dependiences in fragment android studio throw that exception.
When the Hilt annotation processor comes across the #Inject annotation in your fragment it is going to try and find (from a Hilt #Module with a #Provides annotated method) an instance of Flow.
I'm assuming from the error message you have not created this, so instead Hilt will move to the next option and try and create and instance of Flow itself.
So Hilt looks at the first property of Flow, id of type Int. And just like with the injection of Flow in your fragment, it repeats the process. It looks for an instance of Int from a Hilt #Module with a #Provides annotated method. This doesn't exist. Next option, try and construct it itself. Well Int is a platform type, you have no access, so you haven't created an Int class with an #Inject annotated constructor.
Hilt is now out of options and throws this Exception:
Hilt java.lang.Integer cannot be provided without #inject constructor or #Provides - annotated method
With the new dependency injection library Hilt, how to inject some classes into ViewModel without constructor params and ViewModelFactory?
Is it possible?
Like in Fragment, we use only #AndroidEntryPoint and #Inject.
how to inject some classes into ViewModel without constructor params and ViewModelFactory? Is it possible?
Hilt supports constructor injection of ViewModel via the #HiltViewModel (previously #ViewModelInject) annotation.
This allows for any #AndroidEntryPoint-annotated class to redefine their defaultViewModelProviderFactory to be the HiltViewModelFactory, which allows the creation of #HiltViewModel-annotated ViewModels correctly instantiated via Dagger/Hilt.
NEW HILT VERSION:
#HiltViewModel
class RegistrationViewModel #Inject constructor(
private val someDependency: SomeDependency,
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
...
}
OLD HILT VERSION:
class RegistrationViewModel #ViewModelInject constructor(
private val someDependency: SomeDependency,
#Assisted private val savedStateHandle: SavedStateHandle
) : ViewModel() {
...
}
And then
#AndroidEntryPoint
class ProfileFragment: Fragment(R.layout.profile_fragment) {
private val viewModel by viewModels<RegistrationViewModel>() // <-- uses defaultViewModelProviderFactory
Yes, it is possible to inject dependency into a ViewModel class without constructor params. First we need to create a new interface annotated with #EntryPoint to access it.
An entry point is an interface with an accessor method for each
binding type we want (including its qualifier). Also, the interface
must be annotated with #InstallIn to specify the component in which to
install the entry point.
The best practice is adding the new entry point interface inside the class that uses it.
public class HomeViewModel extends ViewModel {
LiveData<List<MyEntity>> myListLiveData;
#ViewModelInject
public HomeViewModel(#ApplicationContext Context context) {
myListLiveData = getMyDao(context).getAllPosts();
}
public LiveData<List<MyEntity>> getAllEntities() {
return myListLiveData;
}
#InstallIn(ApplicationComponent.class)
#EntryPoint
interface MyDaoEntryPoint {
MyDao myDao();
}
private MyDao getMyDao(Context appConext) {
MyDaoEntryPoint hiltEntryPoint = EntryPointAccessors.fromApplication(
appConext,
MyDaoEntryPoint.class
);
return hiltEntryPoint.myDao();
}
}
In the code above we created a method named getMyDao and used EntryPointAccessors to retrieve MyDao from Application container.
Notice that the interface is annotated with the #EntryPoint and it's
installed in the ApplicationComponent since we want the dependency
from an instance of the Application container.
#Module
#InstallIn(ApplicationComponent.class)
public class DatabaseModule {
#Provides
public static MyDao provideMyDao(MyDatabase db) {
return db.MyDao();
}
}
Though the code above has been tested and worked properly but it is not the recommended way to inject dependency into ViewModel by android officials; and unless we know what we're doing, the best way is to inject dependency into ViewModel through constructor injection.
I have the presenter
class PhonePresenter #Inject constructor(
private val preference: DataPreference,
private val ioScheduler: Scheduler = Schedulers.io())
ioScheduler is a default parameter. I want to inject only preference: DataPreference. Now I have the exception
[dagger.android.AndroidInjector.inject(T)] io.reactivex.Scheduler cannot be provided without an #Provides-annotated method.
Is there any way to define parameters which I want to inject in a constructor?
Make inject constructor with secondary constructor
class PhonePresenter(
private val preference: DataPreference,
private val ioScheduler: Scheduler) {
#Inject constructor(preference: DataPreference) : this(preference, Schedulers.io())
}
Dagger is responsible for injection, let it do it's job. Don't use default parameters (Dagger doesn't care), this will force you to make concious decisions about your dependencies.
Two approaches come to mind:
1. Use Dagger to inject
Create a qualifier so Dagger can diferentiate between types of schedulers you might want to inject and a module that provides default IO scheduler.
#Qualifier
#Retention(AnnotationRetention.RUNTIME)
#Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
#MustBeDocumented
annotation class ForIo
#Module
class SchedulerModule {
#Provides #ForIo
fun provideIoScheduler() : Scheduler = Schedulers.io()
}
class PhonePresenter #Inject constructor(
private val preference: DataPreference,
#ForIo private val ioScheduler: Scheduler
) { ... }
Add SchedulerModule to your component as usual.
The correct way to supply different arguments is to use a different component with different modules specialized e.g. for testing. Or when testing you'll call the constructor manually.
2. Avoid Dagger in this case
Alternatively you can remove the IO scheduler from constructor parameters. The names suggests it's never going to be anything else than Schedulers.io() so it makes little sense to make it parameterized; make it an implementation detail instead.
class PhonePresenter #Inject constructor(private val preference: DataPreference) {
private val ioScheduler = Schedulers.io()
...
}