Inject only certain params in constructor - android

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()
...
}

Related

Dagger #Inject and #AssistedInject together

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")
}

How can I use Hilt to inject Retrofit to Repository, which is injected to ViewModel?

I have just learnt manual dependency injection, but I am trying out Hilt to handle these dependency injections.
I want to inject a ViewModel into a Fragment. The fragment is contained within an Activity. Right now, I have added the annotations to Application, Activity, and Fragment.
#HiltAndroidApp
class MovieCatalogueApplication : Application()
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
...
}
#AndroidEntryPoint
class HomeFragment : Fragment() {
private lateinit var binding: FragHomeBinding
private val viewmodel: HomeViewModel by viewModels()
...
As can be seen, my HomeFragment depends on HomeViewModel. I have added a ViewModel injection as described here like so.
class HomeViewModel #ViewModelInject constructor(
private val movieRepository: MovieRepository,
private val showRepository: ShowRepository,
#Assisted private val savedStateHandle: SavedStateHandle
) : ViewModel() {
...
}
However, the ViewModel requires two repositories. Right now, my MovieRepository is like so.
class MovieRepository (private val movieApi: MovieService) {
...
}
In the above code, MovieService will be created by Retrofit using the Retrofit.create(class) method. The interface used to create MovieService is like so.
interface MovieService {
...
}
To get my Retrofit instance, I am using the following code.
object RetrofitService {
...
private var _retrofit: Retrofit? = null
val retrofit: Retrofit
get() {
return when (_retrofit) {
null -> {
_retrofit = Retrofit.Builder()
.client(client)
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
_retrofit!!
}
else -> _retrofit!!
}
}
}
I am not too sure how I can inject the Retrofit into the Repository to be used by my ViewModel later on. Could someone give me some pointers or step-by-step instructions on how to do this?
Apparently, it is not as hard as it seems.
You have to first define the binding information to Hilt. Binding information tells Hilt how to provide the instances of the dependency specified. Because MovieService is created using a Retrofit (which is a 3rd-party class not created by yourself) using the builder pattern, you can't use the constructor injection and you have to instead use Hilt modules and the annotation #Provides to tell Hilt about this binding information.
As described in the doc, the annotated function in the Hilt module you have created will supply the following information to Hilt so that Hilt can provide the instances of the dependency.
• The function return type tells Hilt what type the function provides instances of.
• The function parameters tell Hilt the dependencies of the corresponding type.
• The function body tells Hilt how to provide an instance of the corresponding type. Hilt executes the function body every time it needs to provide an instance of that type.
In the end, you only need to modify the MovieRepository class, add a module for each repository, and annotate the function that tells Hilt how to provide the service instance created with Retrofit with #Provides.
Code.
class MovieRepository #Inject constructor(
private val movieApi: MovieService
) {
...
}
interface MovieService {
...
}
#Module
#InstallIn(ActivityRetainedComponent::class)
object MovieModule {
#Provides
fun provideMovieService(): MovieService
= RetrofitService.retrofit.create(MovieService::class.java)
}
As you can see, the ActivityRetainedComponent is referred in the #InstallIn annotation because the Repository is to be injected to a ViewModel. Each Android component is associated to different Hilt components.

Hilt Inject into ViewModel without constructor params

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.

Dagger 2 constructor injection in kotlin with Named arguments

I have this dependency:
#Singleton
class SpiceMix #Inject constructor(#field:[Named("oregano")] private val oregano: Spice,
#field:[Named("sage")] private val sage: Spice,
#field:[Named("rosemary")] private val rosemary: Spice)
And a module to fulfill its dependencies:
#Module
class SpiceModule {
#Provides
#Named("oregano")
#Singleton
fun provideOregano(): Spice = Oregano()
#Provides
#Named("sage")
#Singleton
fun provideSage(): Spice = Sage()
#Provides
#Named("rosemary")
#Singleton
fun provideRosemary(): Spice = Rosemary()
The SpiceMix is then injected in various locations of my app.
However, this does not compile and I get an error:
Spice cannot be provided without an #Provides-annotated method
I think the #Named annotations do not quite work in my constructor signature. I am not quite sure how I can make it work.
Note: this compiles fine if I ditch the Named annotations and change the types of the constructor parameters to their concrete forms. However, Spice is an interface, and I need it for mocking purposes in my tests.
What can I do?
You want to annotate the constructor parameters if you're doing constructor injection, and not the fields - use the #param: annotation target:
#Singleton
class SpiceMix #Inject constructor(#param:Named("oregano") private val oregano: Spice,
#param:Named("sage") private val sage: Spice,
#param:Named("rosemary") private val rosemary: Spice)
Edit: actually, since the resolution order for annotation targets is
param;
property;
field.
according to the docs, having no annotation target should also annotate the parameter of the constructor. So you can just drop the target altogether:
#Singleton
class SpiceMix #Inject constructor(#Named("oregano") private val oregano: Spice,
#Named("sage") private val sage: Spice,
#Named("rosemary") private val rosemary: Spice)

How to avoid circular dependency with Dagger 2?

I have the following Module :
#Module
class HomeModule(private val context: Context) {
#Provides
fun provideContext() = context
#Provides
fun provideHomeUi(): HomeUi {
return HomeUi()
}
#Provides
#Singleton
fun provideHomePresenter(homeUi: HomeUi): HomePresenter {
return HomePresenter(homeUi)
}
}
Those injected fields in HomeUi.kt
#Inject lateinit var context: Context
#Inject lateinit var presenter: HomePresenter
And this one in HomePresenter.kt
#Inject lateinit var context: Context
Here my Deps Component
#Singleton
#Component(modules = arrayOf(
NetworkModule::class,
HomeModule::class
))
interface Deps {
fun inject(homePresenter: HomePresenter)
fun inject(homeActivity: HomeActivity)
fun inject(homeUi: HomeUi)
}
I am using Dagger 2.10 but a StackOverflowError is thrown. I am looking for a way to avoid my circular dependency.
Note : This is my HomeUi which is infinitely instantiate.
It seems like you'd be calling field injection on HomeUi from within your presenters constructor, thus triggering an infinite loop since neither object can finish being constructed without the other (?). This looks like a really bad approach and you should try to move your dependencies into the objects constructors instead of creating half-finished objects.
Use field injection primarily for objects that you can't create yourself, e.g. with Android framework types. IMHO inject(homeActivity: HomeActivity) should be the only method of your component.
Cyclic dependencies are hard to manage and there is no perfect solution, but you can try things like switching to Provider<HomePresenter> to delay the dependency and be able to resolve it this way.
The following should do what you intended, and please note how I'm using constructor injection instead of having 2 additional methods in the module.
#Singleton
#Component(modules = arrayOf(
NetworkModule::class,
HomeModule::class
))
interface Deps {
fun inject(homeActivity: HomeActivity)
}
#Module
class HomeModule(private val context: Context) {
#Provides
fun provideContext() = context
}
#Singleton
class HomeUi #Inject constructor(presenter : Provider<HomePresenter>, context : Context)
{
// use with presenter.get()
}
#Singleton
class HomePresenter #Inject constructor(homeUi : HomeUi)
Please note that using a Provider<T> is the cheapest way to resolve a cyclic dependency that I know of, but it might not be suited for every situation.
I never used Dagger 2 with kotlin but I use it in java. I usually create my Module with my view as param and my method provideHomeUi() return my view as parameter. With this you shouldnt have a StackOverflowError.
Btw, why are you using Dagger 2 with Kotlin and not a directly library for DI on Kotlin, such as Kodein, Koin etc...
Good luck.

Categories

Resources