SoFunction
Updated on 2025-03-10

Android    Data Binding An error and solution in library module

Remember the big pit that Data Binding encountered in library module

It has been more than half a year since I used Data Binding. From the initial setVariable, replacing findViewById, to a more advanced two-way binding, customizing Adapter and Component, viewing the source code to understand the compilation and running process, it is also a small achievement, and I have not encountered any problems in the implementation of Data Binding itself.

However, recently, in the process of refactoring componentization (see Feng Senlin's "Returning to the Original Intention, From Containerization to Componentization" on MDCC), he encountered a relatively serious bug. I have submitted the issue (#224048) to AOSP. Although it is not troublesome to change it, because it is a gradle plugin, so - -, let Google do it by itself. Hope it can be repaired soon.

Library module generates class

It is easy to enable Data Binding under library module, just like application module, plus:

android { 
 dataBinding {
  enabled = true
 }
}

The corresponding generated binding class will be under the databinding package under the package name specified in the manifest.

pit

So the pitfall is here, and it can't be compiled...

Why? An error says symbol cannot be found...so you can view the generated Binding class under the build of module...? ! How is abstract? Why can't you find those get methods? Although I don't know why we get the ViewModel we set in from the binding class.

WTF?!

What happened

What's going on is still something to be studied.

Is it because we were wrong? What went wrong with Dagger2 generation? Or is it a bug in Data Binding?

Because I have also studied the code for the data binding generation part before, it doesn't take much time to find the problem. I won't talk much here, just look at the corresponding location.

In CompilerChief's writeViewBinderInterfaces:

public void writeViewBinderInterfaces(boolean isLibrary) {
 ensureDataBinder();
 (isLibrary);
}

Corresponding to DataBinder:

public void writerBaseClasses(boolean isLibrary) {
 for (LayoutBinder layoutBinder : mLayoutBinders) {
  try {
   (layoutBinder);
   if (isLibrary || ()) {
    String className = ();
    String canonicalName = () + "." + className;
    if ((canonicalName)) {
     continue;
    }
    ("writing data binder base %s", canonicalName);
    (canonicalName,
      (isLibrary));
    (canonicalName);
   }
  } catch (ScopedException ex){
   (ex);
  } finally {
   ();
  }
 }
}

Here is the LayoutBinder (the real implementation class will call writeViewBinder):

public String writeViewBinderBaseClass(boolean forLibrary) {
 ensureWriter();
 return (forLibrary);
}

You can see that if it is a library module, we will do special compilation without generating a real implementation:

public fun writeBaseClass(forLibrary : Boolean) : String =
 kcode("package ${layoutBinder.`package`};") {
  ()
  nl("import ;")
  nl("import ;")
  nl("import ;")
  nl("public abstract class $baseClassName extends ViewDataBinding {")
  { != null}.forEach {
   tab("public final ${} ${};")
  }
  nl("")
  tab("protected $baseClassName( bindingComponent,  root_, int localFieldCount") {
   { != null}.forEach {
    tab(", ${} ${}")
   }
  }
  tab(") {") {
   tab("super(bindingComponent, root_, localFieldCount);")
   { != null}.forEach {
    tab("this.${} = ${};")
   }
  }
  tab("}")
  nl("")
   {
   if ( != null) {
    val type = ().applyImports(, )
    tab("public abstract void ${}($type ${});")
   }
  }
  tab("public static $baseClassName inflate( inflater,  root, boolean attachToRoot) {") {
   tab("return inflate(inflater, root, attachToRoot, ());")
  }
  tab("}")
  tab("public static $baseClassName inflate( inflater) {") {
   tab("return inflate(inflater, ());")
  }
  tab("}")
  tab("public static $baseClassName bind( view) {") {
   if (forLibrary) {
    tab("return null;")
   } else {
    tab("return bind(view, ());")
   }
  }
  tab("}")
  tab("public static $baseClassName inflate( inflater,  root, boolean attachToRoot,  bindingComponent) {") {
   if (forLibrary) {
    tab("return null;")
   } else {
    tab("return DataBindingUtil.<$baseClassName>inflate(inflater, ${}..${}, root, attachToRoot, bindingComponent);")
   }
  }
  tab("}")
  tab("public static $baseClassName inflate( inflater,  bindingComponent) {") {
   if (forLibrary) {
    tab("return null;")
   } else {
    tab("return DataBindingUtil.<$baseClassName>inflate(inflater, ${}..${}, null, false, bindingComponent);")
   }
  }
  tab("}")
  tab("public static $baseClassName bind( view,  bindingComponent) {") {
   if (forLibrary) {
    tab("return null;")
   } else {
    tab("return ($baseClassName)bind(bindingComponent, view, ${}..${});")
   }
  }
  tab("}")
  nl("}")
 }.generate()
}

So the question is, this one here is just an abstract class used to make library module compile pass, and only generates all variable setter methods, what about getters? What a cheating father?

It seems that Google didn't consider it at all. Are those who write Kotlin less stubborn?

Avoidance plan

In order to enable library module to be compiled (this can generate a real Binding implementation in the application module), we have to avoid using the getter method. Fortunately, the DataBindingAdapter and lambda presenter developed by the previous DataBindingAdapter and lambda presenter can indeed avoid using getter to get viewmodel.

Anyway, I hope Google can fix this problem in the next version. It's just iterator and write an abstract interface.

Thank you for reading, I hope it can help you. Thank you for your support for this site!