I have a simple class as below
class MainString(val msg: String)
I want to inject with different argument to it, so I use the #Named Qualifier as per the guide shown in https://google.github.io/dagger/users-guide
With that my AppModule has
#Provides #Named("Two")
fun provideTwoMainString(): MainString {
return MainString("Two")
}
#Provides #Named("One")
fun provideOneMainString(): MainString {
return MainString("One")
}
And in my MainActivity, I just call
#Inject #Named("One")
lateinit var stringOne: MainString
#Inject #Named("Two")
lateinit var stringTwo: MainString
However, when I compile, it complains
Error:(11, 1) error: com.elyeproj.demo_dagger_scope.MainString cannot be provided without an #Inject constructor or from an #Provides- or #Produces-annotated method.
It seems to want me to provide another Provider without the qualifier. So if I add the below, all will compiles. But it is not of used to me, as I want to have different argument injection.
#Provides
fun provideMainString(): MainString {
return MainString("Solo")
}
What have I done wrong?
Annotation work slightly different on kotlin. look this doc
You have to annotate the field as:
#Inject #field:Named("Two")
lateinit var stringOne: MainString
If you add the following to your qualifier annotation:
#Target(FIELD, VALUE_PARAMETER, FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER)
then you won't have to add "field:"
For example, with:
#Qualifier
#Retention(RUNTIME)
#Target(FIELD, VALUE_PARAMETER, FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER)
annotation class One
you can inject as follows:
#Inject #One lateinit var stringOne: String
Unfortunately #Named doesn't specify the #Target(..), so just create your own annotations. #Named is a bad idea anyway, since it's using string literals.
1) If you are using a qualifier like following, here 'OmdbService'
#Qualifier
public annotation class OmdbService
Then use
#Inject #field:OmdbService lateinit var retrofitOmdbService: Retrofit
2) If are using a named provider like following, here 'orangeservice_retrofit'
#Provides
#OrangeApplicationScope
#Named("orangeservice_retrofit")
fun retrofit(okHttpClient :OkHttpClient, gson : Gson, c :Context): Retrofit {
return Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.client(okHttpClient)
.baseUrl(c.getString(R.string.base_url))
.build()
}
Then use
#Inject #field:Named("orangeservice_retrofit") lateinit var retrofitOrangeService: Retrofit
Related
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.
Whenever I am using #Named annotation in my code I am always getting "java.lang.String cannot be provided without an #Inject constructor or an #Provides-annotated method." this error Please find the code snippets below but before checking the code let me clear you what I want to achieve and what is the exact issue I am facing. So I am trying to keep two functions in my module file which return same type as String for this I am trying to use #Named annotation and after this I am getting the above mentioned error and If I only keep one function for type String then my project is running without any error.
This is my Component Class
#Singleton
#Component(modules = [GeneralModules::class])
interface MainComponent {
fun dummyString():String
}
This is my Module class
#Module
class GeneralModules {
#Provides
#Singleton
#Named("name1")
fun getDummyString1(): String{
return "some name"
}
#Provides
#Singleton
#Named("name2")
fun getDummyString(): String{
return "some name"
}
}
This is how I am using inside the Activity class
#Inject
#field:Named("name1")
lateinit var name1: String
#Inject
#field:Named("name2")
lateinit var name2: String
So if I removed the #Named from Module file, delete one function and remove #field:Named code from activity it will run without any error.
You can use an annotation class with the #Qualifier of javax.inject.Qualifier
Qualifier file
#Qualifier
annotation class Name1
#Qualifier
annotation class Name2
Module Class
#Module
class GeneralModules {
#Provides
#Singleton
#Name1
fun getDummyString1(): String{
return "some name"
}
#Provides
#Singleton
#Name2
fun getDummyString(): String{
return "some name"
}
}
code inside the Activity class
#Inject
#Name1
lateinit var name1: String
#Inject
#Name2
lateinit var name2: String
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)
I have this issue when I try to use Kotlin and Dagger 2 .
"interface cannot be provided without an #Provides- or #Produces-annotated method.”
This is my Module class:
#Module
class MenuActivityModule(#NonNull private val menuActivity: MenuActivity) {
#Provides
#MenuActivityScope
fun provideGameScreenDimensions(application: Application) =
GameScreenDimension(application.resources)
#Provides
#MenuActivityScope
fun provideAudio() =
AndroidAudio(menuActivity)
#Provides
#MenuActivityScope
fun providePowerManager() =
menuActivity.getSystemService(Context.POWER_SERVICE) as PowerManager
#Provides
#MenuActivityScope
fun provideWakeLock(#NonNull powerManager: PowerManager) =
powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, Preferences.APPLICATION_TAG)
}
This is a part of my Activity class, where I inject some variables with Dagger:
class MenuActivity : BaseActivity {
#Inject
lateinit var myAudio: Audio
#Inject
lateinit var wakeLock: PowerManager.WakeLock
#Inject
lateinit var apiService : ApiService
#Inject
lateinit var sharedPref : SharedPreferences
#Inject
lateinit var gameDimension : GameScreenDimension
init {
DaggerMenuActivityComponent.builder()
.menuActivityModule(MenuActivityModule(this))
.build()
.inject(this)
}
//more code
}
Audio.kt is interface and Dagger has problem to inject it. Inside the activity module I am returning AndroidAudio
instance, which implements Audio interface. I am not sure what is the problem here. In Java I have had many times injection of interfaces and I never had this issue before.
If somebody can help me I will be so happy.
Thanks!
I think the solution for your problem is very simple and also not so obvious unfortunately.
Because Kotlin does not require type to be specified on methods return, you can easily write something like this:
#Provides
#MenuActivityScope
fun provideAudio() =
AndroidAudio(menuActivity)
And the compiler will not complain about that, but in this case Dagger will provide AndroidAudio object for injection. In you Activity you are looking for Audio object for injection. So if you change this code to be:
#Provides
#MenuActivityScope
fun provideAudio(): Audio =
AndroidAudio(menuActivity)
Everything should be ОК.
Give a try and tell me if something does not work.
Thanks.
BTW : When I use Dagger with Kotlin I aways specify the type of returned value, because usually that is gonna be the type of the injected variables or the type of the variable which you are going to use in your dagger module.
I do have two custom annotations
#Qualifier
#kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
annotation class IOScheduler
and
#Qualifier
#kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
annotation class UIScheduler
I'm using them in my module
#Module
class UiModule {
#Provides
#UIScheduler
fun provideUiScheduler(): Scheduler = AndroidSchedulers.mainThread()
#Provides
#IOScheduler
fun provideIOScheduler(): Scheduler = Schedulers.io()
}
Of course module is added to my component, and when i'm trying to inject them it says
rx.Scheduler cannot be provided without an #Provides- or
#Produces-annotated method.
injecting
class MainPresenter(val view: MainContract.View) : MainContract.Presenter {
#Inject lateinit var api: ApiServices
#Inject lateinit var databaseManager: DatabaseManager
#Inject #UIScheduler lateinit var observeScheduler: Scheduler
#Inject #IOScheduler lateinit var subscribeScheduler: Scheduler
...
}
Funny think is that i'm using almost same think with injecting Gson for my ApiClient and it works perfectly
#Module
class ApiModule {
...
#Provides
#Singleton
#DefaultGson
fun providesGson(): Gson = GsonBuilder().create()
#Provides
#Singleton
#FormatDateGson
fun providesGsonFormatTimestamp(): Gson = GsonBuilder().setDateFormat(Constants.TIME_STAMP_FORMAT).create()
}
#Singleton
class ApiClient #Inject constructor(#DefaultGson val gson: Gson, val sharedPreferences: SharedPrefsUtils) {
...
}
In additional i can say that i do have same modules / providers structure in my java projects and it works just fine.
Use custom annotations with field: target e.g.
#Inject #field:IOScheduler lateinit var subscribeScheduler: Scheduler
In other news: Dagger refuses to do the following:
#Inject internal var something: Boolean = false // or
#Inject protected var toolbar: Toolbar? = null
The error looks like this:
Dagger does not support injection into private fields
This affects any variables without the lateinit modifier.
You'll have to expose primitives in your component and inject them manually.
Just specify the set: target so injection is done via public/internal/protected setter instead of the private backing field like this:
#set:Inject internal var something: Boolean = false // or
#set:Inject protected var toolbar: Toolbar? = null
Custom annotations
Declare your custom annotations with
#Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
-- pretty much anything except PROPERTY because that is invisible to Java. The compiler will now show an error if you try to use an annotation without get:, set: or field: target. And you need that target so the annotation takes effect.