Defining Android Binding Adapter in Kotlin

Defining Android Binding Adapter in KotlinHerman CheungBlockedUnblockFollowFollowingFeb 6If 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 Javapublic 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}/> .

</layoutThis 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 to convert a Java function with static methods look like this:package myApp// import omittedclass 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 omittedclass 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#bindCurrencyThe 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 omittedpublic final class CurrencyBindingAdapter { public static final class Companion { 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 declarationWe may make CurrencyBindingAdapter itself to be a singleton in Kotlin:package myApp// import omittedobject 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 generate code like this:package myApp;// import omittedpublic 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 staticINSTANCE 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 functionIt 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 generate 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 directlySo 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.

Image credit: https://stocksnap.

io/photo/ZUPM81ZWUX.

. More details

Leave a Reply