Android Data Binding

from (null) to (data)

Xavier Rubio Jansana

 @teknik_tdr
 https://xrubio.com
 https://github.com/xrubioj/

What?

  • Standalone library (no additional dependencies)
  • Compatible with API 7+ (Android 2.1) πŸ™ƒ
  • Announced by Google at I/O 2015
  • Integrated into Android Studio & Gradle
  • Requires changes to the layouts... simple ones 😬
  • ...but can coexist with old methods β†’ progressive πŸŽ‰

Why?

  • Declarative layouts
  • Minimize code to bind data to layouts
  • Cleaner separation
  • Allows observables (reactive)

How?

In app Gradle file:

						android {
						    ...
						    dataBinding {
						        enabled = true
						    }
						}
					
...if you use Kotlin also add:

							apply plugin: 'kotlin-kapt'
							...
							dependencies {
							    ...
							    kapt "com.android.databinding:compiler:2.3.0"
							}
						

Layout


						<?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"
						        xmlns:tools="http://schemas.android.com/tools">

						    <data>
						        <variable
						            name="model"
						            type="com.xrubio.databindingexample.model.PojoActivityModel"/>
						        <variable ... />
						    </data>

						    <!-- Your regular layout here -->

						</layout>
					

Layout syntax


					    <TextView
					        android:id="@+id/message"  android:id="@+id/message" 
					        android:text="@{model.message}"
					        tools:text="message"
					        android:layout_width="wrap_content"
					        android:layout_height="wrap_content"
					        android:layout_marginBottom="16dp"
					        android:layout_marginEnd="16dp"
					        android:layout_marginStart="16dp"
					        android:layout_marginTop="16dp"
					        app:layout_constraintBottom_toBottomOf="parent"
					        app:layout_constraintEnd_toEndOf="parent"
					        app:layout_constraintStart_toStartOf="parent"
					        app:layout_constraintTop_toTopOf="parent"/>
					

Activity


						class PojoModelActivity : AppCompatActivity() {
						    lateinit var binding: ActivityPojoModelActivityPojoModelBinding // activity_pojo_model
						    lateinit var model: PojoModel

						    override fun onCreate(savedInstanceState: Bundle?) {
						        super.onCreate(savedInstanceState)

						        binding = DataBindingUtil.setContentView(
						                                   this, R.layout.activity_pojo_model)

						        model = PojoModel(message = "Hello World!")
						        binding.model = model
						    }
						}
					

Models

Models

  • Can be as simple as a POJO or data class
    
    								data class PojoModel(var message: String)
    							
  • If you try to update this model UI will not update:
    
    								binding.model.message = "You will never see this"
    							
  • To update we need to replace the whole model:
    
    								binding.model = PojoModel("But you will see this")
    							

Models

  • More complex like an Observable
    
    								class ObservableModel() : BaseObservable() {
    								    @get:Bindable@get:Bindable
    								    var message: String? = null
    								        set(message) {
    								            field = message
    								            notifyPropertyChangednotifyPropertyChanged(BR.message)
    								        }
    								}
    							
  • You can update individual fields:
    
    								binding.model.message = "You should see this"
    							

Models

  • Middle ground... ObservableFields
    
    								class ObservableFieldModel() {
    
    								    val message: ObservableField<String> =
    								                    ObservableField()
    								    val visible: ObservableBoolean = ObservableBoolean()
    
    								    constructor(message: String) : this() {
    								        this.message.set(message)
    								    }
    								}
    							
  • You can update individual fields:
    
    								binding.model.message.setset("You should see this")
    							

Bindings

Bindings

  • Bind variables: in/out
  • Bind views: access views directly

Examples


							var binding: ActivityPojoModelBinding

							binding.model = PojoModel("Hi!") // <variable ...>
							binding.text.alpha = 0.5f        // <TextView android:id="@+id/text" ...>
						

Bindings

  • Bind activity layout
    
    						        val binding: ActivityPojoModelBinding =
    						                                DataBindingUtil.setContentView(
    						                                            this, R.layout.activity_pojo_model)
    						        // or...
    						        val binding: ActivityPojoModelBinding =
    						                                ActivityPojoModelBinding.inflate(layoutInflater)
    						        setContentView(binding.root)
    							
  • Bind general layouts
    
    								var binding: ListItemBinding =
    								           DataBindingUtil.inflate(layoutInflater, R.layout.list_item,
    								                                   viewGroup, false)
    								// or...
    								var binding: ListItemBinding = ListItemBinding.inflate(layoutInflater,
    								                                                       viewGroup, false)
    								// ...and finally retrive layout root View
    								binding.root
    							

Variables

  • Expressions are not simple references
  • For example, we can set visibility like this:
    
    								android:visibility="@{model.visible ? View.VISIBLE : View.GONE}"
    								android:text='@{"This is the message: " + model.message}'
    							
  • Null coalescing operator:
    
    								android:text='@{model.message ?? "(no message)"}' // equivalent to Elvis operator ?:
    							
  • Null pointer handling: if model is null
    
    								android:text='@{model.message}' // no crash, evaluates to "(null)"
    								android:visibility="@{model.visible ? View.VISIBLE : View.GONE}" // false => GONE
    							

Operators

Java-like expressions:

  • Mathematical + - / * %
  • String concatenation +
  • Logical && ||
  • Binary & | ^
  • Unary + - ! ~
  • Shift >> >>> <<
  • Comparison == > < >= <=
  • instanceof
  • Grouping ()
  • Literals - character, String, numeric, null
  • Cast
  • Method callsMethod calls
  • Field access
  • Array access [] β†’ Collections android:text="@{list[index]}"
  • Ternary operator ?:

String literals


						android:text='@{map["firstName"]}'
						android:text="@{map[`firstName`}"
						android:text="@{map['firstName']}"
					

Two way bindings
and conversions

Two way bindings

Allows the binding to update the model

  • For example:
    
    							    <EditText android:text="@=={model.value}"/>
    							
  • Needs a setter on the POJO/data class or Observable Model
  • ⚠️ ObservableField doesn't work here
    (weird compilation error πŸ€·β€ )

Conversions

Allows the binding to convert and format data


						<data>
						    <variable name="model"
						              type="com.xrubio.databindingexample.model.TwoWayDataBindingConversionModel"/>
						</data>
						
						<!-- Layout ... -->
						
						<EditText
						    android:text="@={```` + model.value}"/>
					

Simplified conversion ("hackish") 😬

Conversions


						<data>
						    <import type="com.xrubio.databindingexample.converters.IntConverter"/>
						    <variable name="model"
						              type="com.xrubio.databindingexample.model.TwoWayDataBindingConversionModel"/>
						</data>
						
						<!-- Layout ... -->
						
						<EditText
						    android:text="@={IntConverter.INSTANCE.toStringIntConverter.INSTANCE.toString(model.value)}"/>
					
Using converters functions

							object IntConverter {

							    @InverseMethod("toInt")@InverseMethod("toInt")
							    fun toString(value: Int): String {
							        return if (value >= 0)
							            value.toString()
							        else
							            ""
							    }

							    fun toInt(value: String): Int {
							        return try {
							            Integer.parseInt(value)
							        } catch (e: NumberFormatException) {
							            -1
							        }
							    }
							}
						

Event handling

Event handling

Method References β†’ Evaluated at compile time


						<data>
						    <variable name="view"
						              type="com.xrubio.databindingexample.ui.MainActivity"/>
						</data>
						
						<!-- Layout ... -->
						
						<Button
						    android:text="Button"
						    android:onClick="@{view::onClickButton}"/>
					

					    fun onClickButton(view: View) {
					        // ...
					    }
					

Event handling

Listener Bindings β†’ Evaluated at run time


						<data>
						    <variable name="view"
						              type="com.xrubio.databindingexample.ui.MainActivity"/>
						    <variable name="data"
						              type="com.xrubio.databindingexample.ui.ItemData"/>
						</data>
						
						<!-- Layout ... -->
						
						<Button
						    android:text="Button"
						    android:onClick="@{(btn) -> view.onClickButton(btn, data)}"/>
					

					    fun onClickButton(btn: View, data: ItemData) {
					        // ...
					    }
					

Caveats

Caveats

Creation of the layout

New binding not found

Doesn't find new binding on first usage πŸ•΅οΈ
→ Build→Clean Project

Caveats

Annotation processor errors and kapt

Messages don't give information

Not very useful πŸ€·β€ β†’ Check Gradle Console

Caveats

Error messages in Gradle Console lines are off-by-1

e: java.lang.IllegalStateException: failed to analyze: java.lang.RuntimeException: Found data binding errors.
****/ data binding error ****msg:Cannot resolve type for IntConverter file:/Users/teknik/Documents/source/
_talks/android-data-binding-talk/databinding-example/app/src/main/res/layout/
activity_two_way_binding_conversion_converter.xml loc:46:2946:29 - 46:62 ****\ data binding error ****
					
It is really line 47, column 30 πŸ€”

References

Questions? πŸ€”

Thanks! πŸŽ‰

Xavier Rubio Jansana

 @teknik_tdr
 https://xrubio.com
 https://github.com/xrubioj/

This talk is available at:
https://xrubio.com/talks/talk-android-data-binding-from-null-to-data/