Data Binding Basics
What is data binding?
Data binding is a tool for synchronizing screen state and session state in an application. With data binding, a change in the domain model triggers a change in the UI widget. If the data binding is bi-directional, a change in the UI also triggers a change in the domain model.
Where did data binding originate?
In the 1990s popular UI frameworks for client-server development sometimes referred to as forms and controls, first introduced data binding as a concept. Personally, I first used data binding when developing web applications using Java Swing components in the late 90s and early 2000s.
Why use data binding?
Much of the boilerplate required to synchronize the UI and the underlying domain model can be eliminated with data binding leading to cleaner, more concise code.
How does data binding work?
Data binding looks like a cross between token replacement and the Observer pattern. Here is an example of JSF binding used to bind Java Beans to HTML.
Data Binding on Android
The Android Data Binding Library was introduced at Google I/O 2015. It supports similar functionality to data binding in other UI frameworks.
It is bi-directional. Sort of.
When using the Observable
interface, changes to the domain model are immediately reflected in the UI. However changes in the UI are not immediately propagated to the model.
To facilitate flow of information from the UI to the domain model Android data binding also allows you to write expressions in the XML layout file to handle events like onClick
, onTextChanged
, etc.
Let’s look a basic example building on the simple finance calculator demo we have used in previous posts.
Result.kt
First, let’s create a Kotlin Data Class as the domain model for the result of our calculations. For now we’ll just worry about the value and not the color.
data class Result(var value: ObservableInt)
Notice we use the ObservableInt
wrapper class instead of Int
. This ensures changes to our data model will trigger an immediate update in the UI.
A class implementing the
Observable
interface will allow the binding to attach a single listener to a bound object to listen for changes of all properties on that object.
activity_main.xml
Then we add a data element and an expression to the layout file.
<data>
<variable name="result" type="com.example.cleancalc.Result"/>
</data>...<TextView
android:id="@+id/result_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{Integer.toString(result.value)}"
/>
Using an expression we can convert the raw integer result value to a string right in the XML layout.
Next we update the Activity to inflate the layout with data binding and expose the binding to the Presenter via the Controller interface.
MainController.kt
interface MainController {
fun bindData(result: Result)
fun setResultColor(color: Color)
}
MainActivity.kt
class MainActivity : AppCompatActivity(), MainController {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil
.setContentView(this, R.layout.activity_main)
setSupportActionBar(toolbar)
val presenter = MainPresenter(this)
add_button.setOnClickListener {
presenter.onAddButtonClick(
input_text_1.text.toString(),
input_text_2.text.toString())
}
}
override fun bindData(result: Result) {
binding.result = result
}
override fun setResultColor(color: Color) {
val colorInt = if (color == Color.GREEN)
getColor(R.color.green) else getColor(R.color.red)
result_text.setTextColor(colorInt)
}
}
Notice the method setResultText
has been removed as explicitly updating the TextView
is no longer required.
MainPresenter.kt
Finally the Presenter is updated to create an instance of the Result
model and bind it to the Controller. Now when a new sum is calculated its simply set on the Result
model rather than calling controller.setResultText(result)
.
class MainPresenter(private val controller: MainController) {
private val result = Result(ObservableInt(0))
init {
controller.bindData(result)
}
fun onAddButtonClick(input_1: String, input_2: String) {
val calculator = Calculator()
result.value.set(calculator.add(
input_1.toInt(),
input_2.toInt()))
val category = calculator
.getResultCategory(result.value.get())
val color = if (category == Category.HIGH)
Color.GREEN else Color.RED
controller.setResultColor(color)
}
}
That’s it.
In the next post we’ll look at how to register for event handling with expressions.
This post is part of a series on clean architecture that explores how classic software design principles can be applied to modern Android development.
If you found this article helpful, please give it some applause 👏 to help others find it. Feel free to leave a comment below.