How to avoid circular dependency with Dagger 2? - android

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.

Related

Kotlin + Hilt: Class cannot be provided without an #Provides-annotated method when injecting into an object

I was trying to look for similar posts in SO before posting, but most of them talk about retrofit, and my question is about injecting a dependency (Service, Repository or whatever) into an object using #EntryPoint.
I have an object like this:
object FreddieMercuryYouAreTheOne {
lateinit var exception: ExceptionHandler
fun init(appContext: Context) {
setDependencies(appContext)
DoOtherInitStuff...
}
private fun setDependencies(appContext: Context){
val exh = EntryPointAccessors.fromApplication(appContext, Dependencies.ProvideExceptionHandler::class.java)
this.exception = exh.exceptionHandler()
}
/*
* THIS IS JUST AN ABSURD EXAMPLE
* */
private fun DoWhatever(cryptKey16CharStr: String, cryptInitializationVector16CharStr: String) {
try {
doWhatever
}catch(ex: Exception){
exception.logException(ex)
}
}
}
And then I have the class where I set the dependencies:
#Module
#InstallIn(SingletonComponent::class)
class Dependencies {
#EntryPoint
#InstallIn(SingletonComponent::class)
interface ProvideExceptionHandler {
fun exceptionHandler(): ExceptionHandler
}
}
And when building, what I get is the following error:
error: [Dagger/MissingBinding] exception.ExceptionHandler cannot be provided without an #Provides-annotated method.
Well, if I modify my dependencies module as follows:
#Module
#InstallIn(SingletonComponent::class)
class Dependencies {
#Provides
#Singleton
fun bindsExceptionHandler(): ExceptionHandler {
return ExceptionHandler
}
#EntryPoint
#InstallIn(SingletonComponent::class)
interface ProvideExceptionHandler {
fun exceptionHandler(): ExceptionHandler
}
}
Not only build, but it works, and ExceptionHandler is correctly injected in FreddieMercuryYouAreTheOne object, so, as you see, what I have is not exactly an issue, but wondering to know why I need two "providers" to be able to inject a dependency into an object, lets say, why is not enough with interface ProvideExceptionHandler (as Google documentation mentions).
I ask this because I have many class objects across my app, and most of them have dependencies, and so this way I'll have to create two providers for each dependency. Am I doing something wrong?
Entry Points used for field injection for un-supported classes by Hilt like a custom class or content provider.
in your case since you have object FreddieMercuryYouAreTheOne thats can't has a constructer . yeah you need :
1- to Provide the object(instance) you want
in your case:
#Provides
#Singleton
fun bindsExceptionHandler(): ExceptionHandler {
return ExceptionHandler
}
2- and then say hey!! ,i need field injection in my custom class
then you should use :
#EntryPoint
#InstallIn(SingletonComponent::class)
interface ProvideExceptionHandler {
fun exceptionHandler(): ExceptionHandler
}
if you have a normal class you just need to provide the object(just point #1). and then inject it in the constructer.
as i say #EntryPoint for un-supported classes field injection just.
Hint the recommended is constructer-injection over field injection
PLUS: ExceptionHandler and most of dependencies should be injected into ViewModel

When do I need to add annotation when I use Hilt as dependency injection?

I'm reading the article Using Hilt in your Android app.
The Code A, Code B and Code C are from the sample project in solution branch.
I'm confused when I should add the Hilt annotation.
In Code A, the parameter database of fun provideLogDao is injected, but the author doesn't add any annotation, but the parameter appContext of fun provideDatabase is marked with annotation #ApplicationContext, why ?
In Code B, the parameter logDao of class LoggerLocalDataSource is injected, but the author doesn't add any annotation, why?
In Code C, I'm told the following content, why isn't the parameter activity of class AppNavigatorImpl added any annotation? and you know ApplicationContext is predefined binding too, but the author add annotation #ApplicationContext in fun provideDatabase in Code A.
Because an AppNavigator instance is provided in the Activity container , FragmentActivity is already available as a predefined binding.
Code A
#InstallIn(SingletonComponent::class)
#Module
object DatabaseModule {
#Provides
#Singleton
fun provideDatabase(#ApplicationContext appContext: Context): AppDatabase {
return Room.databaseBuilder(
appContext,
AppDatabase::class.java,
"logging.db"
).build()
}
#Provides
fun provideLogDao(database: AppDatabase): LogDao {
return database.logDao()
}
}
Code B
class LoggerLocalDataSource #Inject constructor(private val logDao: LogDao) : LoggerDataSource {
...
}
Code C
class AppNavigatorImpl #Inject constructor(private val activity: FragmentActivity) : AppNavigator {
...
}
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
#Inject lateinit var navigator: AppNavigator
...
}
#ApplicationContext "tells" hilt to inject the application context, and not activity or service contexts. Also reminds us that injecting it will not cause memory leak.
#Inject is right there, at the left of the constructor. In DI terms, is called "Constructor Injection", which means that all of the parameters of the constructor will be injected by hilt without annotating those one by one.
Singleton component's object graph is not being garbage collected until your app is killed - injecting an activity there will cause memory leak.

Is there a way to use injectors in a non-(Activity,Service,Fragment, Application) class

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

Dagger 2 and dependency injection hell?

How do you use dagger from Kotlin?
I've been in a loop of fixing one compile error and moving to another and at the end I get back to step 1
Here is all I need:
AppDependencies
GenericActivityDependencies
PerActivityDependency
Here are my main dependencies:
App
#Module
class ApplicationModule(private val application: Application) {
#Provides
#Singleton
fun provideContext(): Application = this.application
}
#Singleton
#Component(modules = [ HttpModule::class, ApplicationModule::class ])
interface AppComponent {
val app: Application
}
Why do I need to once provide the dependency in the module and another time define it in the component?
Activity Module
#Module
class ActivityModule(private val activity: Activity) {
#PerActivity
#Provides
#ActivityContext
fun provideContext(): Context = activity
}
#Component(modules = [ActivityModule::class], dependencies = [AppComponent::class])
#ActivityContext
interface ActivityComponent {
fun inject(activity: MainActivity)
}
HomeModule
#Module
class LandingModule {
#PerActivity
#Provides
fun provideSomethig(): Something {
return Something()
}
}
#SomeActivity
#Subcomponent(modules = [LandingModule::class])
interface LandingSubcomponent {
val something: Something
}
By this point, I have written more code than there needs to be in my whole activity.
I get errors like can't inherit from a scopes component
Can't generate Dagger gencode
Subcomponent needs a different scope
How do I achieve this?
Is there a better di for kotlin?
Is there a sample somewhere I could follow that has per activity module?
I understand your frustrations. I have been there before and it took me quite some time to understand dagger myself. Just a quick demo/tutorial.
#Singleton
#Component(modules = [AppModule::class])
interface AppComponent {
fun context(): Context
}
#Module
class AppModule(private val application: Application) {
#Provides
#Singleton
fun provideApplication(): Application= application
}
The component is the interface to the container. Anything defined in here can be accessible if you are able to instantiate your container successfully. Also, it is the interface to other containers/components. This means that if you want to expose something outside your container, you define it here. Therefore,
Why the heck do I need to once provide the dependency in the module
and another time define it in the component. This is plain stupid.
is not always true. You don't need to define anything in your component if you don't want anything to expose outside. An alternative to exposing would be injecting.
#Singleton
#Component(modules = [AppModule::class])
interface AppComponent {
fun inject(activity: MainActivity)
}
You are not exposing anything here but you can still get the activity context from the container thru inject.
Now let's proceed to scoping.
Scoping is the way to provide 'local singletons' inside your container. A scoped dependency will be created only once inside the container. An example is your PerActivity scope. A scoped component will only accept a module that is scoped with the same Scope. For example:
#PerActivity
#Component(dependencies = [AppComponent::class],
modules = [ActivityModule::class])
interface ActivityComponent{
fun inject(activity: MainActivity)
}
The corresponding module should only be scoped with PerActivity as well.
class ActivityModule(activity:Activity) {
#PerActivity
#Provides
fun provideActivity() = activity
}
Any other scope defined in your module that is not the same scope as your intended component will result in a compile error. Multiple scopes is not allowed as well.
As for the component dependencies, you can use use dependencies or subcomponents. If dependencies is used, any dependency that is required by the child must be exposed by the parent. In our case above, if the ActivityComponent requires the activity context, the AppComponent must define a function that returns it. In subcomponents, just define your subcomponent in your component and the dependencies will be resolved internally.
I have written a small guide to learning dagger 2. If you are interested, you can go check it out.
https://medium.com/tompee/android-dependency-injection-using-dagger-2-530aa21961b4
https://medium.com/tompee/dagger-2-scopes-and-subcomponents-d54d58511781
Why the heck do I need to once provide the dependency in the module and another time define it in the component. This is plain stupid.
I agree, but don't be discouraged by that fact, because once you learn to master it you'll learn to appreciate it and really take advantage. I've been using version 2.2 for a while without any issues. I've only needed to define one annotation, add a pair of additional dependencies (AutoDagger among them, which takes care of that component feature), and use the following structure:
/* Dagger */
implementation "com.google.dagger:dagger:2.2"
// Fix: github.com/rharter/auto-value-gson/issues/43#issuecomment-219994018
kapt 'com.squareup:javapoet:1.9.0'
kapt "com.google.dagger:dagger-compiler:2.2"
compileOnly 'org.glassfish:javax.annotation:10.0-b28'
/* Autodagger */
kapt "com.github.lukaspili.autodagger2:autodagger2-compiler:1.1"
implementation "com.github.lukaspili.autodagger2:autodagger2:1.1"
DaggerScope.java
#Retention(RetentionPolicy.RUNTIME)
#Scope
#interface DaggerScope {
}
YourApp.kt
#AutoComponent(modules = [YourApp.Module::class])
#AutoInjector
#DaggerScope
class YourApp : Application() {
...
#Singleton #dagger.Module
inner class Module(private val app : YourApp) {
#Provides #AutoExpose(YourApp::class) fun application(): Application = app
#Provides #AutoExpose(YourApp::class) fun context(): Context = app
...
// Stuff like your database or base service can go here
}
}
SomeActivity.kt
#AutoComponent(dependencies = [YourApp::class],
modules = [SomeActivity.Module::class]) // you are free to add other modules here
#AutoInjector
#DaggerScope
class SomeActivity : AppCompatActivity() {
...
#dagger.Module
inner class Module() {
#Provides #AutoExpose(SomeActivity::class) fun something(): Something {
return some way of creating Something
}
/* specific deps for SomeAcitivity's actions, like specific services.
You can also access DAOs as you've got access to the DB */
}
}
You can also mimic this structure with a Fragment instead of an Activity.
Hope this helps you!

Dagger can not provide injection with Kotlin

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.

Categories

Resources