Dagger can not provide injection with Kotlin - android

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.

Related

Dagger hilt injecting into activity results in UninitializedPropertyAccessException error

I am trying to inject a class into an activity using dagger hilt using a Module. I've looked through tutorials and countless SO posts. I cannot tell what I'm doing wrong.
I have a DataStoreManger class that i'm trying to use in an activity.
class DataStoreManager (#ApplicationContext appContext: Context) {...}
I have an AppModule that provides a DataStoreManager.
#Module
#InstallIn(SingletonComponent::class)
object AppModule {
#Provides
#Singleton
fun provideDataStoreManager(#ApplicationContext appContext: Context):
DataStoreManager = DataStoreManager(appContext)
}
Then I'm trying to use DataStoreManager in MainActivity.
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
#Inject lateinit var dataStoreManager: DataStoreManager
private val userPreferencesFlow = dataStoreManager.userPreferencesFlow
}
This results in an uninitialized property access exception
Caused by: kotlin.UninitializedPropertyAccessException: lateinit property
dataStoreManager has not been initialized
When you use
#Inject lateinit var dataStoreManager
The dataStoreManager is only actually injected during the call to super.onCreate().
However, when you create a property such as
private val userPreferencesFlow = dataStoreManager.userPreferencesFlow
This property is created immediately as part of the construction of your MainActivity class - it doesn't wait until onCreate() runs, hence the error you are receiving.
Generally, the easiest way to avoid this is by only access the userPreferencesFlow from your injected manager when you actually want to collect it, which is presumably after super.onCreate().
However, if you want to still create your flow as a member variable at the activity level, you can use by lazy:
private val userPreferencesFlow by lazy {
dataStoreManager.userPreferencesFlow
}
The lazy block will only be executed lazily - e.g., the first time you actually use the member variable, thus ensuring that your #Inject variable has actually been injected.

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.

Android - Kotlin - Dagger 2 - Qualifier Annotation with Member Injection [duplicate]

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

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.

rx.Scheduler cannot be provided without an #Provides- or #Produces-annotated method

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.

Categories

Resources