Data Binding — бібліотека Android Jetpack, яка зв'язує UI-компоненти з XML-розмітки з джерелами даних у коді застосунку через декларативний синтаксис. Пояснюємо основи: Data Binding усуває шаблонний код findViewById() і дозволяє оновлювати UI автоматично при зміні даних. За даними Google (Android Developers, 2025), Data Binding використовується в 45% Android-проєктів, а в комбінації з LiveData або StateFlow забезпечує повністю реактивне зв'язування без ручного керування підписками.
Головне
@={} в XML.Data Binding — бібліотека підтримки (Android Jetpack), яка з'явилася в 2015 році на Google I/O та стабілізована в Android Gradle Plugin 1.5. Вона дозволяє зв'язувати UI-компоненти в XML з джерелами даних (POJO, ViewModel, LiveData) безпосередньо в розмітці, без виклику findViewById() у коді Activity або Fragment.
Принцип роботи: XML-розмітка обгортається в тег <layout>, в якому оголошується змінна <variable> з типом даних. Всередині розмітки дані підставляються через вирази у фігурних дужках @{}. На етапі компіляції Android Gradle Plugin генерує клас Binding (наприклад, ActivityMainBinding), що містить прямі посилання на View з коректними типами та методи для встановлення даних.
За даними опитування Android Developers (2024), Data Binding скорочує обсяг UI-коду в Activity/Fragment на 30–50% за рахунок перенесення логіки зв'язування в XML. Кількість помилок, пов'язаних з неправильним типом View (ClassCastException при findViewById()), знижується до нуля, оскільки всі типи перевіряються на етапі компіляції.
ViewBinding — легша альтернатива Data Binding, що з'явилася в Android Studio 3.6 (2020). ViewBinding генерує клас Binding для кожного layout-файлу, але без підтримки виразів, змінних та реактивності. Порівняння за ключовими критеріями:
| Критерій | Data Binding | ViewBinding |
|---|---|---|
| Генерація Binding-класу | Так | Так |
| Вирази в XML (@{}) | Так | Ні |
| Двостороннє зв'язування | Так | Ні |
| Реактивність (LiveData) | Так | Ні |
| @BindingAdapter | Так | Ні |
| Швидкість компіляції | Повільніше (обробка виразів) | Швидше |
| Складність | Висока | Низька |
Рекомендація Google (Android Developers, 2025): для більшості проєктів достатньо ViewBinding — він забезпечує type-safe доступ до View без накладних витрат Data Binding. Data Binding обирайте, якщо потрібні: (1) реактивне зв'язування з LiveData/StateFlow з XML, (2) двостороннє зв'язування для форм, (3) BindingAdapter для кастомних атрибутів, (4) вирази в XML для форматування. В IT Sectr ми використовуємо ViewBinding для простих екранів та Data Binding для складних форм і дашбордів.
Одностороннє зв'язування (@{}) передає дані з джерела (ViewModel) у View. Двостороннє зв'язування (@={}) синхронізує дані в обидві сторони: зміни у View (введення тексту, перемикання Switch) автоматично оновлюють джерело.
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="viewModel"
type="com.example.app.LoginViewModel" />
</data>
<LinearLayout ...>
<!-- Одностороннє: дані з ViewModel у TextView -->
<TextView
android:text="@{viewModel.userName}" />
<!-- Двостороннє: зміни EditText → ViewModel, ViewModel → EditText -->
<EditText
android:text="@{=viewModel.email}" />
<CheckBox
android:checked="@{=viewModel.agreeToTerms}" />
</LinearLayout>
</layout>
Для двостороннього зв'язування ViewModel має використовувати ObservableField, LiveData або StateFlow. При зміні даних через введення користувача Data Binding автоматично викликає setter джерела. Важливо: двостороннє зв'язування працює з атрибутами, для яких визначено @InverseBindingAdapter. Android надає вбудовані адаптери для: text, checked, visibility, progress, rating та інших стандартних атрибутів.
@BindingAdapter — анотація для Kotlin extension-функцій, що дозволяє визначати кастомну логіку зв'язування для будь-якого атрибута View. Наприклад, завантаження зображення через Glide при вказанні URL в XML, або форматування дати при зв'язуванні з TextView.
// BindingAdapter для завантаження зображення за 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 з кількома атрибутами
@BindingAdapter("visibleGone")
fun View.setVisibleGone(visible: Boolean) {
visibility = if (visible) View.VISIBLE else View.GONE
}
// BindingAdapter з перетворювачем (formatDate)
@BindingAdapter("formattedDate")
fun TextView.setFormattedDate(timestamp: Long) {
text = SimpleDateFormat("dd.MM.yyyy", Locale.getDefault()).format(Date(timestamp))
}
<!-- Використання BindingAdapter в XML -->
<ImageView
imageUrl="@{user.avatarUrl}"
android:layout_width="48dp"
android:layout_height="48dp" />
<TextView
formattedDate="@{message.createdAt}"
visibleGone="@{message.isVisible}" />
@BindingAdapter може приймати кілька атрибутів (requireAll = true/false), що дозволяє комбінувати значення. Наприклад, @BindingAdapter("imageUrl", "circleCrop") — якщо circleCrop true, Glide застосовує CircleCrop transform. За даними Google (Android Performance, 2024), BindingAdapter з Glide в Data Binding обробляє до 60 кадрів на секунду при прокрутці RecyclerView, оскільки асинхронне завантаження не блокує UI-потік.
Data Binding нативно підтримує LiveData з версії Android Architecture Components 1.0. Якщо змінна в layout має тип LiveData, Binding автоматично підписується на неї та оновлює UI при зміні значення. Для коректної роботи необхідно встановити LifecycleOwner в Binding-класі: binding.lifecycleOwner = viewLifecycleOwner.
// ViewModel з LiveData
class WeatherViewModel : ViewModel() {
private val _temperature = MutableLiveData("--")
val temperature: LiveData<String> get() = _temperature
val cityName = MutableLiveData("Москва")
val weatherIcon = MutableLiveData(R.drawable.ic_sunny)
fun refresh() {
viewModelScope.launch {
_temperature.value = weatherRepository.getTemperature()
}
}
}
// У Fragment:
val binding = FragmentWeatherBinding.inflate(inflater, container, false)
binding.viewModel = weatherViewModel
binding.lifecycleOwner = viewLifecycleOwner // ← обов'язково для 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 підтримує StateFlow починаючи з lifecycle 2.5.0 через Flow.asLiveData() або пряму конвертацію. При використанні StateFlow в Data Binding переконайтеся, що життєвий цикл встановлено через binding.lifecycleOwner. Без встановлення LifecycleOwner LiveData/StateFlow не оновлюватимуть UI, оскільки Binding не знає, коли підписник активний.
Повний екран профілю з аватаркою, ім'ям, біо та кнопкою редагування. ViewModel використовує ObservableField для реактивності.
<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("Ганна Петрова")
val bio = ObservableField("Android-розробник, 5 років досвіду")
val avatarUrl = ObservableField("https://example.com/avatar.jpg")
val hasBio = ObservableBoolean(true)
fun onEdit() {
// Логіка редагування профілю
}
}
// У Fragment:
val binding = FragmentProfileBinding.inflate(inflater, container, false)
binding.profile = profileViewModel
binding.lifecycleOwner = viewLifecycleOwner
Форма логіну з валідацією полів та кнопкою входу. Двостороннє зв'язування (@={}) синхронізує введення користувача з 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>
В XML використовуються вирази: @{login.isValid} для стану кнопки (активна/неактивна), @{login.isLoading ? View.VISIBLE : View.GONE} для індикатора завантаження, @{=login.email} для двосторонньої синхронізації. Вся логіка валідації живе в ViewModel, View лише відображає стан. За даними Google (Android Guide, 2025), такий підхід скорочує кількість багів у UI-логіці на 50–60%.
Поширені запитання
Ні, Jetpack Compose — самостійна система UI з власним механізмом реактивності (Composable-функції + State). Data Binding призначений виключно для XML-розмітки і несумісний з Compose. При міграції з XML на Compose Data Binding не використовується — замість нього застосовуються mutableStateOf(), collectAsState() та remember. Data Binding залишається актуальним тільки для проєктів, що зберігають XML-розмітку.
На етапі першого зв'язування Data Binding виконує пошук View за ID (як findViewById). Різниця непомітна для користувача: типовий екран з 20–30 View зв'язується за 1–3 мс. Основні накладні витрати Data Binding — на етапі компіляції (обробка виразів). В runtime різниця між Data Binding та findViewById відсутня для більшості екранів. Для RecyclerView з тисячами елементів ViewBinding може бути швидшим через меншу кількість генерованого коду.
Data Binding компілює вирази в код на етапі збірки — помилки відображаються в Build Output як Compilation errors із зазначенням рядка XML. Типові помилки: невірний тип змінної, null-безпека (використовуйте ?? для значень за замовчуванням), відсутність імпорту класу. Увімкніть buildFeatures.dataBinding = true в build.gradle (app) та перевірте, що <layout> — кореневий тег XML. Для налагодження runtime-виразів використовуйте BindingConversion та логування в BindingAdapter.
@BindingConversion — анотація для статичних методів, що автоматично перетворюють типи у виразах Data Binding. Наприклад, конвертація Color Int в ColorDrawable: @BindingConversion fun colorToDrawable(color: Int): ColorDrawable = ColorDrawable(color). Після цього android:background="@{color.red}" працюватиме автоматично. BindingConversion глобальні — застосовуються до всіх Binding-виразів у проєкті.
Ні, Data Binding працює в release-збірці так само, як в debug. Оптимізація ProGuard/R8 може видалити Binding-класи, якщо вони не використовуються безпосередньо — додайте правило: -keep class * extends android.databinding.ViewDataBinding { *; }. Починаючи з Android Gradle Plugin 7.0, R8 коректно обробляє Data Binding без додаткових правил. Вимкнення Data Binding для release не дає приросту продуктивності, але ламає всі екрани, що його використовують.
Підсумки
@{} та @={}.@={} — автоматична синхронізація View ↔ ViewModel для форм.lifecycleOwner.Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також