Dagger can not find classes generated by other annotation processor
Asked Answered
P

3

14

I have written a simple Annotation Processor (just for fun) that will generate some boilerplate code that I have been writing in my previous project. It actually generates a module like following by collecting annotations on Activity classes

@Module
abstract class ActivityInjectorModule {
  @ContributesAndroidInjector
  abstract fun providesMain2Activity(): Main2Activity

  @ContributesAndroidInjector
  abstract fun providesMainActivity(): MainActivity
}

However, when I run it with dagger, dagger can't seem to find classes generated by my annotation processor. Although, class is generated and present in generated directory, I can use it in my source code but on compilation, dagger produces the following exception. Any expert suggestion?

error: cannot find symbol
@dagger.Component(modules = {dagger.android.AndroidInjectionModule.class, com.mallaudin.daggietest.di.AppModule.class, ActivityInjectorModule.class})
                                                                                                                       ^
  symbol: class ActivityInjectorModule

This is the main app component.

@Singleton
@Component(
    modules = [
        AndroidInjectionModule::class,
        AppModule::class,
        ActivityInjectorModule::class
    ]
)
interface AppComponent : AndroidInjector<App> {


    @Component.Builder
    interface Builder {

        fun addContext(@BindsInstance ctx: Context): Builder

        fun build(): AppComponent
    }
}

ActivityInjectorModule class is generated by annotation processor and exists in the generated directory.

Application class

class App : DaggerApplication() {
    override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
        return DaggerAppComponent.builder().addContext(this).build()
    }
}

Everything works perfectly, if I create the generated class myself. Somehow on compile time, dagger is unable to find the class when generated by my annotation processor.

After Yuriy Kulikov's answer,

yurily's answer

You can see generated file is in the same package but also referenced with fully qualified name. Still dagger reports errors.

Here is the link to github repository if someone wants to experiment

Palaver answered 27/7, 2019 at 18:50 Comment(6)
Can you please share your application class or where you define all your module?Certified
@ShwetaChauhan I have updated the code. Please have a look.Palaver
App class extend DaggerApplication() right?Certified
I am already doing this. Actually I have searched a lot about this issue.I think there is some tricky part as I am not sure about ordering of annotation processors, may be dagger does something before compilation.Palaver
That's because perhaps your code compiles after dagger compiles. For that you should make sure for the opposite to happenTremolite
Even if I do that for the first time, it works, for next rounds there is not guarantee about ordering of annotation processors.Palaver
L
11

Solution:

  1. Generate java code. Kapt does not support multiple rounds.
  2. Write generated files on earliest possible round.

Explanation:

Javac annotation processor uses rounds instead of defining processors order. So normally the simplified algorithm is like that:

  1. Gather all java sources
  2. Run all annotation processors. Any annotation processor can generate new files using Filer.
  3. Gather all generated files and if there are any, run step 2 again.
  4. If there are no files generated, run one more round where RoundEnvironment.processingOver() returns true, signaling this is the last round.

Here is a pretty good explanation of the process

Now a bit about kapt. Kapt uses javac to run annotation processors. To make it possible, it runs kotlin compliler first to generate java stub files and runs javac on them. Currently kapt does not support multiple rounds, meaning it does not generate java stubs for kotlin classes, generated by annotation processors. Note: javac still uses multiple rounds, it just can't pick up generated kotlin sources.

So, back to your question. One possible option is to move your generated classes into a separate module like it's described here.

But the easiest option is to generate java code directly and your generated java classes will be picked up by javac automatically, launching second round of annotation processing, where dagger will process them.

Just a few more notes:

  • Do not generate your code when RoundEnvironment.processingOver() == true, it will not trigger another round. Generate it during the same round you see the annotation.
  • To make the generated code visible to annotation processor, write it using Filer.
Leduc answered 4/8, 2019 at 19:50 Comment(1)
Oh man! It works with java perfectly. Also the last notes were very helpful.Palaver
C
5

New answer I have somehow missed that you are using kapt. Kapt can process your classes, even without full qualified name (which is remarkable) if you add this to your build.gradle:

kapt {
    arguments {
        arg("argumentIncremental", 'true')
    }

    correctErrorTypes = true

}

More info about this: https://kotlinlang.org/docs/reference/kapt.html#non-existent-type-correction


Previous answer can be useful is someone has the same issue with annotationProcessor (apt) in gradle.

Short answer: use fully qualified name for ActivityInjectorModule:

@dagger.Component(modules = {dagger.android.AndroidInjectionModule.class, com.mallaudin.daggietest.di.AppModule.class, com.mallaudin.daggietest.di.ActivityInjectorModule.class})

Alternatively put both files in the same package.

Long answer: Dagger is an annotation processor, it runs before your code is compiled and (potentially) before your other annotation processor runs. The sequence in which processors run is not defined.

Dagger annotation processor will process the TypeElement annotated with @dagger.Component and it will try to find all modules including the "ActivityInjectorModule.class". The thing is, ActivityInjectorModule might not have been generated yet. Therefore "ActivityInjectorModule" will not have a package at this point. Dagger will assume that ActivityInjectorModule resides in the same package as the Component class and will not add an import. The usual workaround for this is to use full-qualified names for generated classes, if they are used by other annotation processors. Sometimes it makes sense to move annotation processing to a difference gradle module, but I don't this that this is what you want.

Clyster answered 3/8, 2019 at 12:38 Comment(8)
interestingly it doesn't work with fully qualified names also. I have tried it also. As I was observing the code generated by gradle, I realized this thing and also put files in same packages.Palaver
I have updated the question with screenshot. Kindly have a look.Palaver
I have added link to github repoPalaver
Hey, thanks for the link. I have realized that you are using kapt. See updated answer (it works).Clyster
Thanks @Yuriy it works now but not every time. Sometimes it does work. sometime it doesn'tPalaver
You can check it by first cleaning the project and building again.Palaver
Well in this case I can only think of switching the argumentIncremental to false. You can also try Koin :-)Clyster
Tried already :) I believe this is caused by ordering of annotation processors which we can not guarantee. Would love to explore Koin.Palaver
L
0

There may be a more elegant way to solve this, but the simplest and most reliable solution is to do two passes with javac—once to run just your annotation processor, and the second to do everything it normally does.

The javac documentation specifies two options which should help you out.

-proc: {none,only}

Controls whether annotation processing and/or compilation is done. -proc:none means that compilation takes place without annotation processing. -proc:only means that only annotation processing is done, without any subsequent compilation.

-processor class1[,class2,class3...]

Names of the annotation processors to run. This bypasses the default discovery process.

The first pass (to run only your own annotation processor) is

javac -proc:only -processor com.foo.bar.MyProcessor MyProject/src/*

and the second pass (a regular build) is

javac MyProject/src/*

If you’re using something like Ant or Maven, you should be able to update the build instructions to have two compiler passes with only a minimal amount of effort.

Edit: here’s my attempt at Gradle instructions

I have no experience with Gradle, but it seems like you need to do something like this.

In your Gradle build script, you need to define the preprocessing task and add a dependency on your task to the javaCompile task.

javaCompile.dependsOn myAnnotationTask

task myAnnotationTask(type: JavaCompile) {
    options.compilerArgs << '-proc:only' << '-processors com.foo.bar.MyAnnotationProcessor'
}
Langsdon answered 2/8, 2019 at 5:19 Comment(1)
I’ve included my best effort attempt at Gradle instructions.Langsdon

© 2022 - 2024 — McMap. All rights reserved.