Skip to content

DataBinding / ViewBinding incremental compilation

DataBinding and ViewBinding both read layouts at compile time and generate binding classes that application code can use directly. ViewBinding primarily turns view IDs into type-safe fields. DataBinding also processes variables, expressions, <include>, and custom attributes, generating supporting code such as BindingImpl, mapper, and BR.

A full Gradle build passes layout info, generated source, and annotation processor inputs between resource processing and source compilation. Jugg reuses these intermediate artifacts from the latest Gradle build and reprocesses only layouts changed in the current Run. The resource stage prepares XML and generation inputs for aapt2, and the source stage then generates and compiles binding-related code.

How DataBinding / ViewBinding generates code from layouts

Both mechanisms use layouts as input but generate different scopes:

MechanismLayout content it usesMain generated result
ViewBindingLayout file and views with IDsBinding class for the layout, including view fields and inflate / bind entry points
DataBinding<layout>, variables, expressions, <include>, bindable fields, and custom attributesStripped XML, layout info, binding base/implementation, mapper, and BR

A ViewBinding layout still enters aapt2 as an ordinary Android resource, while the generated binding class proceeds as Java source. DataBinding first converts binding expressions into stripped XML that aapt2 can process and writes variable, expression, and include relationships into layout info. The official DataBinding annotation processor then generates BindingImpl, mapper, and BR.

text
layout source file
  -> DataBinding / ViewBinding layout processing
  -> XML usable by aapt2 + layout info / binding source
  -> aapt2 generates resource artifacts
  -> annotation processor generates DataBinding source when needed
  -> Java/Kotlin compilation generates classes and DEX

How Jugg performs two-stage incremental processing

Jugg does not rerun complete DataBinding Gradle tasks. It reads layout info, BR, setter stores, and generated binding artifacts from the Gradle baseline and rebuilds only the parts affected by current layout changes:

text
changed layout + Gradle baseline
  -> resource stage updates XML, layout info, and binding generation inputs
  -> XML enters aapt2 resource compilation
  -> generated source and DataBinding trigger source enter the source stage
  -> source stage generates mapper / BR / BindingImpl
  -> binding-related source compiles with current Java/Kotlin source

The resource stage prepares XML and binding inputs

ViewBinding does not require a DataBinding mapper. It generates a binding class during the resource stage, then passes the class as ordinary Java source to language compilation; the original layout continues through resource compilation.

Expressions and binding tags in a DataBinding layout cannot be sent to aapt2 unchanged. The resource stage generates stripped XML without binding information as the resource input, saves layout info, and generates a trigger source used only to start the DataBinding annotation processor.

The source stage generates DataBinding code

The DataBinding source stage cannot initialize an empty workspace. Mapper, BindingImpl, and BR depend on layout info just generated by the resource stage, so the two stages share current state:

text
resource stage outputs layout info and trigger source
  -> supply layout info affected by <include>
  -> run DataBinding mapper annotation processing
  -> generate BindingImpl, mapper, BR, and Java source
  -> compile them with current Java/Kotlin source into classes and DEX

Mapper uses the official DataBinding processor through Java APT by default. Jugg prepares layouts, classpath, setter stores, and trigger source but does not derive BindingImpl or BindingAdapter method signatures itself.

Why a Kotlin BindingAdapter needs a setter store first

When processing custom attributes, the DataBinding mapper first needs declaration types and method information for BindingAdapter. A Java BindingAdapter can be processed by mapper APT in the same Run. Metadata for a Kotlin BindingAdapter must first be generated as a setter store through the project KAPT.

text
detect a Kotlin BindingAdapter declaration
  -> generate only the current module setter store in an isolated project KAPT environment
  -> compile the Kotlin adapter class
  -> after class success, merge the current store with the Gradle baseline and previous incremental store
  -> mapper APT generates BindingImpl and mapper from the merged store

The setter store is published only after the Kotlin adapter class compiles successfully. If metadata generation or adapter source compilation fails, an ineffective declaration does not enter the next incremental baseline.

The isolated KAPT uses the project's Kotlin compiler and DataBinding dependencies and does not inherit compiler state from the Android Studio host process. It handles only BindingAdapter metadata; mapper itself still uses Java APT.

How cross-run state prevents misaligned output

DataBinding incremental processing maintains several kinds of state across Runs:

  • Layout info connects the two stages. The source stage continues from layout info produced by the resource stage and must not clear it before mapper runs.
  • <include> impact is supplied recursively from layout info. When an included layout changes, Jugg recovers related inputs from layout info in the current and dependency modules rather than scanning only current XML text.
  • BR fields preserve existing order. New fields append to the end of BR from the Gradle baseline so that BR IDs in deployed code do not shift after reordering.
  • Setter stores use Gradle results as the baseline. Current module results merge with that module's Gradle store and previous valid incremental results. Mapper also loads a valid incremental or Gradle store from each direct project dependency, together with stores from dependency AARs. Old incremental cache is not reused after the Gradle baseline changes.
  • Layout info retains a stable backup. If a newly added layout is later deleted, a subsequent full Gradle build still needs consistent layout information, so Jugg preserves a recoverable baseline.

Failure retry and boundaries

  • Mapper retries once only when a Kotlin class is missing. If mapper APT fails and the current Run contains Kotlin source, Jugg compiles the Kotlin class first and reruns mapper once. A second failure preserves the final exception and does not loop.
  • Ordinary ViewBinding does not enter mapper. A module with only ViewBinding generates binding classes and proceeds to language compilation without generating DataBinding mapper or BR.
  • Gradle baseline files must exist. If layout info, BR, or setter stores are missing, Jugg cannot reliably recover from empty state. Run the corresponding Gradle build to refresh the baseline.
  • AGP versions change intermediate artifact locations. Jugg matches known candidate directories. After upgrading AGP, enabling binding for the first time, or changing related Gradle configuration, complete a Gradle build or Sync first.
  • Deleting or renaming a BindingAdapter requires a complete baseline. The current incremental store supports adding declarations and modifying them within the same declaring class. Run Gradle to clear old metadata after removing every declaration or changing the declaring class name.
  • Layout deletion follows the resource deletion boundary. Stripped XML, layout info, and the resource table must be rebuilt; deleting old state cannot rely only on a current overlay.