Defining Android Binding Adapter in Kotlin

Herman Cheung
Feb 6 · 4 min read

If you have used Android DataBinding for presenting data to views, chances are that you have defined some BindingAdapter annotated methods to convert data from your data model to view.

A usual BindingAdapter method is a just a static method, and perform data conversion and injection for specific custom attribute in layout file. For example:

package myApp;
// import omitted
// OK I know it is Java
public class CurrencyBindingAdapter {
@BindingAdapter("currency")
public static void bindCurrency(TextView view, float amount) {
view.setText("$"+amount)
}
}

Then we may have layout look like this

<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
...... <variable name="account" type="MyAccount"/> <TextView
... layout attributes omitted ...
app:currency="@{account.balance}/>
......</layout

This note is about Kotlin but the example above is in Java. While Kotlin is blessed by Google as the first-class citizen of Android development, the DataBinding code generation process does not really understand Kotlin. Instead it understand Kotlin just like any other Java code, with the help of Kotlin interoperability design with Java. So things get tricky when it come to static methods.

The most straight forward way to convert a Java function with static methods look like this:

package myApp
// import omitted
class CurrencyBindingAdapter {
companion object {
@BindingAdapter("currency")
fun bindCurrency(view: TextView, amount: Float) {
view.text = "\$$amount"
}
}
}

Unfortunately it does not work, and we will get compilation errors during code generation task. Methods in companion object of Kotlin is not really static method, but methods of inner class Companion which is instantiated as singleton within the outer class.

public final class CurrencyBindingAdapter {
public static final class Companion {
void bindCurrency(TextView view, float amount) {
...... // the real meat of the method
}
}
}

There is no real static method. So the DataBinding code generator cannot find it. But there is an easy fix for that, we may add an @JvmStatic annotation to those methods:

package myApp
// import omitted
class CurrencyBindingAdapter {
companion object {
@BindingAdapter("currency")
@JvmStatic
fun bindCurrency(view: TextView, amount: Float) {
view.text = "\$$amount"
}
}
}

So it work! Code generator identifies the method correctly. But things changed since Android Gradle Plugin 3.3. When compiling the app with that gradle plugin, we see the warning like this:

w: warning: Binding adapter AK(android.widget.TextView, myapp.CurrencyBindingAdapter) already exists for bindCurrency! Overriding myapp.CurrencyBindingAdapter.Companion#bindCurrency with myapp.CurrencyBindingAdapter#bindCurrency

The code still work but the build output is ugly. One may get dozens of warnings like that for non-trivial app. What’s going on? It turn out that the above Kotlin class generate Java class like this:

package myApp;
// import omitted
public final class CurrencyBindingAdapter {
public static final class Companion {
static void bindCurrency(TextView view, float amount) {
...... // the real meat of the method
}
}
public static bindCurrency(TextView view, float amount) {
Companion.bindCurrency(view, amount);
}
}

So there is no more singleton, However, the Companion class still there and the actual static method of CurrencyBindingAdapter is delegated to Companion class. From DataBinding code generation perspective, we have two classes with the bindCurrency method. One is myApp.CurrencyBindingAdapter.bindCurrency and the other is myApp.CurrencyBindingAdapter$Companion.bindCurrency . That is the warning about.

There are two ways to get rid of the warning.

Method 1: Object declaration

We may make CurrencyBindingAdapter itself to be a singleton in Kotlin:

package myApp
// import omitted
object CurrencyBindingAdapter {
@BindingAdapter("currency")
@JvmStatic
fun bindCurrency(view: TextView, amount: Float) {
view.text = "\$$amount"
}
}

It works flawlessly. Kotlin generates a public static final class with static methods that annotated with @JvmStatic . The only difference from hand code Java class is an additional static reference INSTANCE in the class refer to an instance of itself. Kotlin compiler generates code like this:

package myApp;
// import omitted
public final class CurrencyBindingAdapter {
public static final class INSTANCE =
new CurrencyBindingAdapter();
public static bindCurrency(TextView view, float, amount) {
....
}
}

The @JvmStatic annotation is still vital and cannot go without it. Without it, the bindCurrency is not a Java static method, and it is referenced via the static INSTANCE variable, which DataBinding code generator cannot recognise it.

public final class CurrencyBindingAdapter {
public static final class INSTANCE =
new CurrencyBindingAdapter();
public bindCurrency(TextView view, float amount) {
....
}
}

Method 2: Package function

It is actually a simpler method. In Kotlin we may just declare functions in source code without any class declaration. Kotlin generates classes in the name of the source code file suffixed by Kt with (real) static methods in the class.

So we may have something like this for BindingAdapter methods:

package myApp;
// import omitted
@BindingAdapter("currency")
fun bindCurrency(view: TextView, amount: Float) {
......
}

It is simple, neat, no @JvmStatic annotation and no warning. A class MyAppKt is generated to host the method as static. The only downside is Android Studio does not have a function to search for package methods directly. So mentally we missed a layer to organise those BindingAdapter methods. Further, it may pollute the result of autocomplete of IDE with those methods you may not expect to invoke directly.

So how about extension method, can we make an extension method as Binding Adapter method?

package myApp;
// import omitted
@BindingAdapter("currency")
fun TextView.bindCurrency(amount: Float) {
......
}

The answer is yes, as Kotlin compiler generates the same method as above non-extension version of package function. It may save some key strokes if we need many accesses to methods and properties of the target class. It is matter of taste to use extension method or package function.

Herman Cheung

Written by

Java and Android developer

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade