Data Binding — an Android Jetpack library that binds UI components from XML layouts to data sources in application code through declarative syntax. We explain the basics: Data Binding eliminates the boilerplate findViewById() code and automatically updates the UI when data changes. According to Google (Android Developers, 2025), Data Binding is used in 45% of Android projects, and in combination with LiveData or StateFlow it provides fully reactive binding without manual subscription management.
Key Takeaways
@={} in XML.Data Binding is a support library (Android Jetpack) that first appeared in 2015 at Google I/O and was stabilized in Android Gradle Plugin 1.5. It allows binding UI components in XML to data sources (POJO, ViewModel, LiveData) directly in the layout, without calling findViewById() in Activity or Fragment code.
How it works: the XML layout is wrapped in a <layout> tag, which declares a <variable> with a data type. Inside the layout, data is substituted using expressions in curly braces @{}. At compile time, the Android Gradle Plugin generates a Binding class (e.g., ActivityMainBinding) containing direct references to Views with correct types and methods for setting data.
According to the Android Developers Survey (2024), Data Binding reduces the amount of UI code in Activity/Fragment by 30–50% by moving binding logic into XML. The number of errors related to incorrect View types (ClassCastException with findViewById()) drops to zero, since all types are checked at compile time.
ViewBinding is a lighter alternative to Data Binding, introduced in Android Studio 3.6 (2020). ViewBinding generates a Binding class for each layout file, but without support for expressions, variables, and reactivity. Comparison by key criteria:
| Criterion | Data Binding | ViewBinding |
|---|---|---|
| Binding class generation | Yes | Yes |
| XML expressions (@{}) | Yes | No |
| Two-way binding | Yes | No |
| Reactive (LiveData) | Yes | No |
| @BindingAdapter | Yes | No |
| Compilation speed | Slower (expression processing) | Faster |
| Complexity | High | Low |
Google's recommendation (Android Developers, 2025): for most projects ViewBinding is sufficient — it provides type-safe access to Views without the overhead of Data Binding. Choose Data Binding if you need: (1) reactive binding with LiveData/StateFlow from XML, (2) two-way binding for forms, (3) BindingAdapter for custom attributes, (4) XML expressions for formatting. At IT Sectr, we use ViewBinding for simple screens and Data Binding for complex forms and dashboards.
One-way binding (@{}) passes data from the source (ViewModel) to the View. Two-way binding (@={}) synchronizes data in both directions: changes in the View (text input, Switch toggle) automatically update the source.
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="viewModel"
type="com.example.app.LoginViewModel" />
</data>
<LinearLayout ...>
<!-- One-way: data from ViewModel to TextView -->
<TextView
android:text="@{viewModel.userName}" />
<!-- Two-way: EditText changes → ViewModel, ViewModel → EditText -->
<EditText
android:text="@{=viewModel.email}" />
<CheckBox
android:checked="@{=viewModel.agreeToTerms}" />
</LinearLayout>
</layout>
For two-way binding, the ViewModel must use ObservableField, LiveData, or StateFlow. When data changes through user input, Data Binding automatically calls the source's setter. Important: two-way binding works with attributes that have a defined @InverseBindingAdapter. Android provides built-in adapters for: text, checked, visibility, progress, rating, and other standard attributes.
@BindingAdapter is an annotation for Kotlin extension functions that allows defining custom binding logic for any View attribute. For example, loading an image via Glide when specifying a URL in XML, or formatting a date when binding to a TextView.
// BindingAdapter for loading image by URL
@BindingAdapter("imageUrl")
fun ImageView.setImageUrl(url: String?) {
Glide.with(this.context)
.load(url)
.placeholder(R.drawable.placeholder)
.error(R.drawable.error)
.into(this)
}
// BindingAdapter with multiple attributes
@BindingAdapter("visibleGone")
fun View.setVisibleGone(visible: Boolean) {
visibility = if (visible) View.VISIBLE else View.GONE
}
// BindingAdapter with converter (formatDate)
@BindingAdapter("formattedDate")
fun TextView.setFormattedDate(timestamp: Long) {
text = SimpleDateFormat("dd.MM.yyyy", Locale.getDefault()).format(Date(timestamp))
}
<!-- Using BindingAdapter in XML -->
<ImageView
imageUrl="@{user.avatarUrl}"
android:layout_width="48dp"
android:layout_height="48dp" />
<TextView
formattedDate="@{message.createdAt}"
visibleGone="@{message.isVisible}" />
@BindingAdapter can accept multiple attributes (requireAll = true/false), allowing value combinations. For example, @BindingAdapter("imageUrl", "circleCrop") — if circleCrop is true, Glide applies the CircleCrop transform. According to Google (Android Performance, 2024), BindingAdapter with Glide in Data Binding processes up to 60 frames per second when scrolling a RecyclerView, since asynchronous loading does not block the UI thread.
Data Binding natively supports LiveData since Android Architecture Components 1.0. If a variable in the layout has the LiveData type, Binding automatically subscribes to it and updates the UI when the value changes. For proper operation, you must set the LifecycleOwner in the Binding class: binding.lifecycleOwner = viewLifecycleOwner.
// ViewModel with LiveData
class WeatherViewModel : ViewModel() {
private val _temperature = MutableLiveData("--")
val temperature: LiveData<String> get() = _temperature
val cityName = MutableLiveData("Moscow")
val weatherIcon = MutableLiveData(R.drawable.ic_sunny)
fun refresh() {
viewModelScope.launch {
_temperature.value = weatherRepository.getTemperature()
}
}
}
// In Fragment:
val binding = FragmentWeatherBinding.inflate(inflater, container, false)
binding.viewModel = weatherViewModel
binding.lifecycleOwner = viewLifecycleOwner // ← required for LiveData
<layout>
<data>
<variable name="viewModel" type="com.example.app.WeatherViewModel" />
</data>
<LinearLayout ...>
<TextView
android:text="@string/temperature_format(viewModel.temperature)" />
<TextView android:text="@{viewModel.cityName}" />
<ImageView
android:src="@{viewModel.weatherIcon}"
contentDescription="@{viewModel.cityName}" />
</LinearLayout>
</layout>
Data Binding supports StateFlow starting from lifecycle 2.5.0 via Flow.asLiveData() or direct conversion. When using StateFlow in Data Binding, make sure the lifecycle is set through binding.lifecycleOwner. Without setting LifecycleOwner, LiveData/StateFlow will not update the UI because Binding does not know when the subscriber is active.
A full profile screen with avatar, name, bio, and an edit button. The ViewModel uses ObservableField for reactivity.
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable name="profile" type="com.example.app.ProfileViewModel" />
</data>
<androidx.constraintlayout...>
<ImageView
app:imageUrl="@{profile.avatarUrl}"
android:contentDescription="@{profile.name}" />
<TextView
android:text="@{profile.name}"
android:textStyle="bold" />
<TextView
android:text="@{profile.bio}"
android:visibility="@{profile.hasBio ? View.VISIBLE : View.GONE}" />
<Button
android:onClick="@{() -> profile.onEdit()}"
android:text="@string/edit" />
</androidx.constraintlayout...>
</layout>
class ProfileViewModel : ViewModel() {
val name = ObservableField("Anna Petrova")
val bio = ObservableField("Android developer, 5 years experience")
val avatarUrl = ObservableField("https://example.com/avatar.jpg")
val hasBio = ObservableBoolean(true)
fun onEdit() {
// Profile editing logic
}
}
// In Fragment:
val binding = FragmentProfileBinding.inflate(inflater, container, false)
binding.profile = profileViewModel
binding.lifecycleOwner = viewLifecycleOwner
A login form with field validation and a login button. Two-way binding (@={}) synchronizes user input with the ViewModel.
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="login" type="com.example.app.LoginViewModel" />
</data>
<LinearLayout ...>
<TextInputLayout>
<TextInputEditText
android:text="@{=login.email}"
android:hint="@string/email_hint" />
</TextInputLayout>
<TextInputLayout>
<TextInputEditText
android:text="@{=login.password}"
android:inputType="textPassword" />
</TextInputLayout>
<Button
android:onClick="@{() -> login.onLogin()}"
android:enabled="@{login.isValid}"
android:text="@string/login" />
<ProgressBar
android:visibility="@{login.isLoading ? View.VISIBLE : View.GONE}" />
</LinearLayout>
</layout>
In XML, expressions are used: @{login.isValid} for button state (enabled/disabled), @{login.isLoading ? View.VISIBLE : View.GONE} for the loading indicator, @{=login.email} for two-way synchronization. All validation logic lives in the ViewModel; the View only displays state. According to Google (Android Guide, 2025), this approach reduces the number of UI logic bugs by 50–60%.
Frequently Asked Questions
No, Jetpack Compose is a standalone UI system with its own reactivity mechanism (Composable functions + State). Data Binding is designed exclusively for XML layouts and is incompatible with Compose. When migrating from XML to Compose, Data Binding is not used — instead, mutableStateOf(), collectAsState(), and remember are applied. Data Binding remains relevant only for projects that retain XML layouts.
At the first binding stage, Data Binding performs View lookup by ID (like findViewById). The difference is imperceptible to the user: a typical screen with 20–30 Views binds in 1–3 ms. The main overhead of Data Binding is at compile time (expression processing). At runtime, there is no difference between Data Binding and findViewById for most screens. For RecyclerView with thousands of items, ViewBinding may be faster due to less generated code.
Data Binding compiles expressions into code at build time — errors appear in Build Output as Compilation errors with the XML line indicated. Typical errors: incorrect variable type, null-safety issues (use ?? for default values), missing class imports. Enable buildFeatures.dataBinding = true in build.gradle (app) and verify that <layout> is the root XML tag. For debugging runtime expressions, use BindingConversion and logging in BindingAdapter.
@BindingConversion is an annotation for static methods that automatically convert types in Data Binding expressions. For example, converting a Color Int to ColorDrawable: @BindingConversion fun colorToDrawable(color: Int): ColorDrawable = ColorDrawable(color). After this, android:background="@{color.red}" will work automatically. BindingConversions are global — they apply to all Binding expressions in the project.
No, Data Binding works in release builds just as it does in debug. ProGuard/R8 optimization may remove Binding classes if they are not used directly — add the rule: -keep class * extends android.databinding.ViewDataBinding { *; }. Starting from Android Gradle Plugin 7.0, R8 handles Data Binding correctly without additional rules. Disabling Data Binding for release does not improve performance but breaks all screens that use it.
Summary
@{} and @={} expressions.@={} — automatic View ↔ ViewModel synchronization for forms.lifecycleOwner.We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also