70

With the recent versions of dagger 2 one of the improvements made are the possibility of having static provide methods. Simply so:

@Provides
static A providesA() {
  return A();
}

I was wondering how does one go about doing this in kotlin? I've tried

@Module
class AModule {
  companion object {
    @JvmStatic
    @Provides
    fun providesA(): A = A()
  }
}

But I get the error message:

@Provides methods can only be present within a @Module or @ProducerModule

I'm guessing there's something going on here with the companion object, however I'm quite new to Kotlin and I'm unsure of how one can do this. Is it even possible?

Thanks!

Fred
  • 16,367
  • 6
  • 50
  • 65
  • There's also some discussion on this topic on https://www.reddit.com/r/androiddev/comments/75rc85/keeping_the_daggers_sharp_square_corner_blog/do8ktb3/ there's a third option - top level functions. – arekolek Oct 12 '17 at 17:13
  • This may be cause to the plugin you are using. Example, Dagger (Madrappss). But still this work. – Enciyo Aug 21 '21 at 06:52
  • @Enciyo I've asked this question around 4 years ago. Since then dagger has come a long way and now supports this. At the time, it didn't... hence the question. – Fred Aug 22 '21 at 07:37

5 Answers5

61

Although I think zsmb13's solution is better, I found another solution which works

@Module
class AModule {
  @Module
  companion object {
    @JvmStatic
    @Provides
    fun providesA(): A = A()
  }

  // add other non-static provides here
}

However, note that there will be two generated classes: AModule_ProvidesAFactory and AModule_Companion_ProvidesAFactory rather than the one AModule_ProvidesAFactory class for the case with an object instead of a class with a companion object

arekolek
  • 9,128
  • 3
  • 58
  • 79
Omar El Halabi
  • 2,118
  • 1
  • 18
  • 26
50

I can't test it right now, but I think this should work:

@Module
object AModule {
    @JvmStatic
    @Provides
    fun providesA(): A = A()
}
zsmb13
  • 85,752
  • 11
  • 221
  • 226
  • @Fred, could you accept this answer if it solves your problem? – Lajos Arpad Jul 09 '17 at 11:14
  • sorry, completely forgot about that – Fred Jul 09 '17 at 16:55
  • 5
    But having `object` wont it keep this singleton class for way too long? Just tested with memory dump, and even after killing activity and totally removing it from heap I still have my `object AModule` inside memory, not exactly what we would want to... – user2141889 Aug 02 '17 at 17:14
  • 1
    Wouldn't the class you have your static provider methods inside in Java stick around the same way? – zsmb13 Aug 02 '17 at 17:50
  • @zsmb13 I have also checked it, and it didnt keep anything after activity was destroyed, module was simple class with static methods, without any fields or anything else – user2141889 Aug 02 '17 at 18:06
  • 3
    I do not think this solution works well, because making your AModule "object" not "class" will create a lot of singleton modules around the app, and they will exist even if you do not need this module instance any more, for example if you have screen scopes. Once you access your screen the module singleton will be created and it will be never cleaned because it is "object" – Stoycho Andreev Oct 14 '17 at 21:15
  • _Once you access your screen the module singleton will be created and it will be never cleaned because it is "object"_ - Won't it be cleaned when the class-loader that loaded `AModule` gets garbage collected? – arekolek Mar 16 '18 at 06:44
  • 2
    This solution is better as Jake Wharton says "_don't use `companion object` for modules. Use `object`. In that case, the instance will be unused and its initialization code will be removed by R8 and the methods will be truly static and can also be inlined just like Java._". https://github.com/google/dagger/issues/900#issuecomment-410041915 – Alex Mar 25 '19 at 12:52
  • @zsmb13 I can't make field injection work with qualifiers. Any idea why? https://github.com/google/dagger/issues/1604 – GuilhE Sep 04 '19 at 14:59
  • @zsmb13 I've also created an SO question about this. If you want to answer I'll mark as the correct answer. Regards. https://stackoverflow.com/questions/57779791/qualifier-causes-dagger-missingbinding – GuilhE Sep 04 '19 at 15:52
22

Now Dagger2 (version 2.26) support companion objects in @Module annotated classes in kotlin withthouh @Module and @JvmStatic annotations

Better support for binding declarations within Kotlin companion objects of @Module annotated classes.

Update dagger dependencies to 2.26 version as

def dagger_version = "2.26"
//dagger
implementation "com.google.dagger:dagger:$dagger_version"
kapt "com.google.dagger:dagger-compiler:$dagger_version"

//If you're using classes in dagger.android you'll also want to include:
implementation "com.google.dagger:dagger-android:$dagger_version"
implementation "com.google.dagger:dagger-android-support:$dagger_version"
kapt "com.google.dagger:dagger-android-processor:$dagger_version"

so now you can use

@Module
class AModule {

  companion object {

    @Provides
    fun providesA(): A = A()
  }

}

Important: Soon, adding @Module on companion objects in @Module classes will raise an error.

Note: For backwards compatibility, we still allow @Module on the companion object and @JvmStatic on the provides methods. However, @Module on the companion object is now a no-op and all of its attributes (e.g. "includes") will be ignored. In future releases, we will make it an error to use @Module on a companion object.

Pavneet_Singh
  • 36,884
  • 5
  • 53
  • 68
18

A great explanation which seems to be Google-approved is at https://github.com/google/dagger/issues/900

Specifically, see:

Static provides can be achieved via @JvmStatic. There are two scenarios I see this come up:

top-level objects

@Module object DataModule {   
  @JvmStatic @Provides fun 
    provideDiskCache() = DiskCache() 
} 

If you have an existing class module, things get a bit weirder

@Module abstract class DataModule {   
    @Binds abstract fun provideCache(diskCache: DiskCache): Cache

    @Module   
    companion object {
        @JvmStatic @Provides fun provideDiskCache() = DiskCache()   
    } 
} 

The way this works is as follows:

the companion object must also be annotated as @Module under the hood, the kotlin compiler will duplicate those static provides methods into the DataModule class. Dagger will see those and treat them like regular static fields. Dagger will also see them in the companion object, but that "module" will get code gen from dagger but be marked as "unused". The IDE will mark this as such, as the provideDiskCache method will be marked as unused. You can tell IntelliJ to ignore this for annotations annotated with @Provides via quickfix

sophia
  • 395
  • 5
  • 13
6

For the static only approach, I like the solution of zsmb13.

However, I came here because I wanted to combine @Provides and @Binds within one module. This is not directly possible but with two nested modules (as Omar Al Halabi pointed out).

I took a slightly different approach for combining @Provides and @Binds:

@Module(includes = [MyModule.Bindings::class])
object MyModule {
    @Module
    interface Bindings {
        @Binds
        fun bindA(a: AImpl): A
    }

    @Provides
    @JvmStatic
    fun provideB(): B = BImpl()
}

The differences are:

  • The outer module is an object providing the static functions. This spares using the unintentional companion object.
  • The inner module holds the abstract bindings. I can use an interface here, which spares the abstract modifier in both the class and the function.
  • The outer module includes the inner module so I don't have to include the inner module elsewhere.
Peter F
  • 3,633
  • 3
  • 33
  • 45