dagger: [ksp] Dagger fails to resolved ViewBinding classes generated by the Android Gradle plugin

I have a class that uses ViewBinding as a generic parameter:

@AndroidEntryPoint
public class DownloadsFragment : BindingFragment<DownloadsListBinding>(DownloadsListBinding::inflate) {

   @Inject
    internal lateinit var db: Database
}

DownloadsListBinding is a class generated from a xml layout resource by the Android Gradle Plugin, and everything is working fine with kapt, but when trying to use KSP, the compilation fails with the following error:

e: [ksp] InjectProcessingStep was unable to process 'db' because 'error.NonExistentClass' could not be resolved.

Dependency trace:
    => element (CLASS): mypackage.downloads.DownloadsFragment
    => type (DECLARED superclass): mypackage.ui.BindingFragment<error.NonExistentClass>
    => type (ERROR type argument): error.NonExistentClass

If type 'error.NonExistentClass' is a generated type, check above for compilation errors that may have prevented the type from being generated. Otherwise, ensure that type 'error.NonExistentClass' is on your classpath.

Versions: Gradle Plugin version: 8.1.1 Kotlin: 1.9.10 KSP: 1.9.10-1.0.13 Hilt: 2.48

About this issue

  • Original URL
  • State: open
  • Created 10 months ago
  • Reactions: 22
  • Comments: 18 (3 by maintainers)

Most upvoted comments

Ups! I was thinking of DataBinding then which I believe has an annotation processor component that runs in KAPT for Kotlin sources. Sorry for the confusion. 😅

So, ViewBinding being a task in AGP then my guess is that it needs some extra wiring so that generates classes are part of KSP’s inputs. I’ll ask AGP team and investigate more…

We’re working on a proper fix for the issue by exposing an API for generated java sources by the android gradle plugin to be consumed by KSP gradle plugin. Meanwhile, you can add the following snippet to your build file as a workaround to wire databinding/viewbinding generated classes to the ksp task.

androidComponents {
    onVariants(selector().all(), { variant ->
        afterEvaluate {
            // This is a workaround for https://issuetracker.google.com/301245705 which depends on internal
            // implementations of the android gradle plugin and the ksp gradle plugin which might change in the future
            // in an unpredictable way.
            project.tasks.getByName("ksp" + variant.name.capitalize() + "Kotlin") {
                def dataBindingTask = (com.android.build.gradle.internal.tasks.databinding.DataBindingGenBaseClassesTask) project.tasks.getByName("dataBindingGenBaseClasses" + variant.name.capitalize())

                ((org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompileTool) it).setSource(
                        dataBindingTask.sourceOutFolder
                )
            }
        }
    })
}

ViewBinding does not rely on KAPT. It is its own code-generating task created by AGP.

Hey @danysantiago

Glad to hear that you are investigating this issue. Is there any progress on this one?

As others pointed out, this is not an issue only of View Binding. BuildConfig, SafeArgs, AIDL, etc. are affected just the same. Here’s a script covering all of them.

/*
 * AGP tasks do not get properly wired to the KSP task at the moment.
 * As a result, KSP sees `error.NonExistentClass` instead of generated types.
 *
 * https://github.com/google/dagger/issues/4049
 * https://github.com/google/dagger/issues/4051
 * https://github.com/google/dagger/issues/4061
 * https://github.com/google/dagger/issues/4158
 */
androidComponents {
    onVariants(selector().all()) { variant ->
        afterEvaluate {
            val variantName = variant.name.capitalize()
            val ksp = "ksp${variantName}Kotlin"
            val viewBinding = "dataBindingGenBaseClasses$variantName"
            val buildConfig = "generate${variantName}BuildConfig"
            val safeArgs = "generateSafeArgs$variantName"
            val aidl = "compile${variantName}Aidl"

            val kspTask = project.tasks.findByName(ksp)
                as? org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompileTool<*>
            val viewBindingTask = project.tasks.findByName(viewBinding)
                as? com.android.build.gradle.internal.tasks.databinding.DataBindingGenBaseClassesTask
            val buildConfigTask = project.tasks.findByName(buildConfig)
                as? com.android.build.gradle.tasks.GenerateBuildConfig
            val aidlTask = project.tasks.findByName(aidl)
                as? com.android.build.gradle.tasks.AidlCompile
            val safeArgsTask = project.tasks.findByName(safeArgs)
                as? androidx.navigation.safeargs.gradle.ArgumentsGenerationTask

            kspTask?.run {
                viewBindingTask?.let { setSource(it.sourceOutFolder) }
                buildConfigTask?.let { setSource(it.sourceOutputDir) }
                aidlTask?.let { setSource(it.sourceOutputDir) }
                safeArgsTask?.let { setSource(it.outputDir) }
            }
        }
    }
}

I’ve made a simple minimum reproducible app, in case it helps: https://github.com/ZOlbrys/HiltKspViewBindingTestApp

The error only happens when you use view bindings in a generic manner, FWIW.

Ah, apologies. I have this configured as a plugin. As a pure DSL within your app’s build.gradle.kts, you should be able to do the same thing as suggested by others above:

import com.android.build.gradle.internal.tasks.databinding.DataBindingGenBaseClassesTask
import org.gradle.api.UnknownTaskException
import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompileTool

androidComponents {
  onVariants(selector().all()) { variant ->
    afterEvaluate {
      project.tasks.getByName("ksp${variant.name.capitalize()}Kotlin") {
        val dataBindingTask =
          try {
            val taskName = "dataBindingGenBaseClasses${variant.name.capitalize()}"
            project.tasks.getByName(taskName) as DataBindingGenBaseClassesTask
          } catch (e: UnknownTaskException) {
            return@getByName
          }

        project.tasks.getByName("ksp${variant.name.capitalize()}Kotlin") {
          (this as AbstractKotlinCompileTool<*>).setSource(dataBindingTask.sourceOutFolder)
        }
      }
    }
  }
}

This work, thanks a lot!

Ah, apologies. I have this configured as a plugin. As a pure DSL within your app’s build.gradle.kts, you should be able to do the same thing as suggested by others above:

import com.android.build.gradle.internal.tasks.databinding.DataBindingGenBaseClassesTask
import org.gradle.api.UnknownTaskException
import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompileTool

androidComponents {
  onVariants(selector().all()) { variant ->
    afterEvaluate {
      project.tasks.getByName("ksp${variant.name.capitalize()}Kotlin") {
        val dataBindingTask =
          try {
            val taskName = "dataBindingGenBaseClasses${variant.name.capitalize()}"
            project.tasks.getByName(taskName) as DataBindingGenBaseClassesTask
          } catch (e: UnknownTaskException) {
            return@getByName
          }

        project.tasks.getByName("ksp${variant.name.capitalize()}Kotlin") {
          (this as AbstractKotlinCompileTool<*>).setSource(dataBindingTask.sourceOutFolder)
        }
      }
    }
  }
}