Building Android components with ease

In the past, I used multiple frameworks to create custom Android components and I wasn’t really happy about my code. A lot of boilerplate code was required and in the end my code became a mess. But now I found a good combination of frameworks to build custom views with less code. The following is my favorite combination.
- Kotlin
- RxJava
- RxBinding
- Data Binding
Why Kotlin?
Kotlin is a modern programming language. By using Kotlin I saved a lot of boilerplate code without having any disadvantages, e.g. bad IDE support, worse debugging nor high effort to configure my first Kotlin App. With AndroidStudio or IntelliJ, I can even copy code from a Java class and paste it to a Kotlin class and I get Kotlin code. Nice! You can read more about it here and here.
Why RxJava?
Observables help you to synchronize the view with your model and reacting on user interactions becomes easier. There is also an adapter for Retrofit and many other frameworks. One example, if you wanna create an autocomplete component and it should debounce the user input. Just use debounce 😛
1 2 3 |
val observable: Observable<String> = ... observable.debounce(200, MILLISECONDS).subscribe { Log.e(javaClass.name, "Debounced!") } |
I needed several weeks to familiarize myself with RxJava, but it was worth the effort.
Why RxBinding?
RxBinding is an extension of RxJava and provides some adapters to the Android view world. You get rid of some really ugly Android classes like TextWatcher.
1 2 3 4 5 6 7 8 9 10 11 12 |
val view: TextView = ... view.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { Log.e(javaClass.name, "Hello, world!") } override fun afterTextChanged(s: Editable?) {} }) |
1 2 3 |
val view: TextView = ... RxTextView.textChanges(view).subscribe { Log.e(javaClass.name, "Hello, world!") } |
Why Data Binding?
Data Binding is a library to minimize glue code which is necessary to bind application logic with layouts and offers some other advantages. One of these is, that I’m able to bypass attributes from the include to the merge tag inside my layouts. With this ability I’m able to create more powerful components without Kotlin code and I can already see a preview within my IDE.
The next cool feature of DataBinding is the BindingAdapter. With a BindingAdapter I’m able to extend a given Android view layout with some custom attributes. No need to subclass a Android view in Kotlin. You’ll see the feature in my example.
The combination in action
Enough theory, let’s start creating a component. In the picture you can see the behavior of the component.
It’s a text input with a label. The label moves up if you tab into the field and the current value is shown as a hint. I’m using the TextInputLayout and extend it with a pattern validation. In this example, I’m validating fuel prices. What do I need to build something like this?
The first step is to create a BindingAdapter. This function extends the TextInputLayout by the attributes pattern and message. It will validates the input after 500 milliseconds without user interaction. If the value doesn’t match the pattern an error message is shown.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
object PatternValidation { @JvmStatic @BindingAdapter("pattern", "message") fun setPattern(layout: TextInputLayout, pattern: String?, message: String?) { layout.editText?.takeIf { pattern?.isNotBlank() ?: false }?.let { val patternCompiled = Pattern.compile(pattern) RxTextView.textChanges(it) .debounce(500, MILLISECONDS) .map { !patternCompiled.matcher(it).matches() } .observeOn(AndroidSchedulers.mainThread()) .doOnNext { layout.isErrorEnabled = it } .filter { it } // Add only the error message if it is an error .subscribe { layout.error = message ?: " " } } } } |
Next step is to create a color for the hint text. The hint should disappear if the component loses focus because the label will move at this position.
1 2 3 4 5 |
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:color="@android:color/transparent" android:state_focused="false"/> <item android:color="?android:textColorHint"/> </selector> |
The last piece of the puzzle is the layout.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <variable name="hint" type="java.lang.String"/> <variable name="label" type="java.lang.String"/> <variable name="message" type="java.lang.String"/> <variable name="pattern" type="java.lang.String"/> <variable name="text" type="java.lang.String"/> </data> <merge> <android.support.design.widget.TextInputLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="2dp" android:layout_marginTop="2dp" android:animateLayoutChanges="true" android:hint="@{label}" app:message="@{message}" app:pattern="@{pattern}"> <android.support.design.widget.TextInputEditText style="@style/Text" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@{hint}" <!-- The = character will generate a 2 way binding. The text is not only updated by the model, the model will also be updated. It is useful for user input fields. --> android:text="@={text}" android:textColorHint="@color/edit_text_hint_color"/> </android.support.design.widget.TextInputLayout> </merge> </layout> |
How to use the component?
1 2 3 4 5 6 7 |
<include layout="@layout/edit_text_view" app:hint="@{`1.42⁹`}" app:label="@{`Super`}" app:message="@{`Invalid fuel price`}" app:pattern="@{`[0-9].[0-9]*|$^`}" app:text="@={yourObservable}"/> |
Conclusion
With all of these frameworks, you are able to build your own component with less effort. Your actual logic is encapsulated in a component and easy to test. View classes like activities or fragment need not to be adapted.
If you have not used one of these libraries, you now know what you should try next, and maybe you’ll like it, too. For those who had no idea, now you have a good starting point to build your custom components. Last but not least, just leave a comment if you are using another (or even better) tool stack how to write custom components with ease.
Recent posts






Comment article