Data Binding — основи, двостороннє зв'язування в Android

Автор: IT Sectr Опубліковано: 2026-02-20 Час читання: 8 хв

Data Binding — бібліотека Android Jetpack, яка зв'язує UI-компоненти з XML-розмітки з джерелами даних у коді застосунку через декларативний синтаксис. Пояснюємо основи: Data Binding усуває шаблонний код findViewById() і дозволяє оновлювати UI автоматично при зміні даних. За даними Google (Android Developers, 2025), Data Binding використовується в 45% Android-проєктів, а в комбінації з LiveData або StateFlow забезпечує повністю реактивне зв'язування без ручного керування підписками.

Головне

  • Data Binding — бібліотека Jetpack для декларативного зв'язування XML-розмітки з джерелами даних через layout-теги.
  • Двостороннє зв'язування — автоматична синхронізація даних між View та ViewModel через @={} в XML.
  • @BindingAdapter — кастомний сетер для атрибутів View, що дозволяє розширювати стандартний Data Binding.
  • Data Binding + LiveData — реактивність без Observer: зміни LiveData автоматично оновлюють UI.
  • Data Binding генерує клас Binding (наприклад, ActivityMainBinding) на етапі компіляції, забезпечуючи type-safe доступ до View.

Що таке Data Binding в Android?

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()), знижується до нуля, оскільки всі типи перевіряються на етапі компіляції.

Data Binding vs ViewBinding: що вибрати

ViewBinding — легша альтернатива Data Binding, що з'явилася в Android Studio 3.6 (2020). ViewBinding генерує клас Binding для кожного layout-файлу, але без підтримки виразів, змінних та реактивності. Порівняння за ключовими критеріями:

КритерійData BindingViewBinding
Генерація 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) автоматично оновлюють джерело.

xml
<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: кастомні атрибути та перетворювачі

@BindingAdapter — анотація для Kotlin extension-функцій, що дозволяє визначати кастомну логіку зв'язування для будь-якого атрибута View. Наприклад, завантаження зображення через Glide при вказанні URL в XML, або форматування дати при зв'язуванні з TextView.

kotlin
// 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))
}
xml
<!-- Використання 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 та StateFlow

Data Binding нативно підтримує LiveData з версії Android Architecture Components 1.0. Якщо змінна в layout має тип LiveData, Binding автоматично підписується на неї та оновлює UI при зміні значення. Для коректної роботи необхідно встановити LifecycleOwner в Binding-класі: binding.lifecycleOwner = viewLifecycleOwner.

kotlin
// 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
xml
<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 не знає, коли підписник активний.

Приклади коду: Data Binding на Kotlin та XML

Приклад 1: Екран профілю з Data Binding

Повний екран профілю з аватаркою, ім'ям, біо та кнопкою редагування. ViewModel використовує ObservableField для реактивності.

xml
<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>
kotlin
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

Приклад 2: Форма логіну з двостороннім зв'язуванням

Форма логіну з валідацією полів та кнопкою входу. Двостороннє зв'язування (@={}) синхронізує введення користувача з ViewModel.

xml
<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%.

Поширені запитання

Чи можна використовувати Data Binding з Compose?

Ні, Jetpack Compose — самостійна система UI з власним механізмом реактивності (Composable-функції + State). Data Binding призначений виключно для XML-розмітки і несумісний з Compose. При міграції з XML на Compose Data Binding не використовується — замість нього застосовуються mutableStateOf(), collectAsState() та remember. Data Binding залишається актуальним тільки для проєктів, що зберігають XML-розмітку.

Чи повільніший Data Binding за findViewById?

На етапі першого зв'язування Data Binding виконує пошук View за ID (як findViewById). Різниця непомітна для користувача: типовий екран з 20–30 View зв'язується за 1–3 мс. Основні накладні витрати Data Binding — на етапі компіляції (обробка виразів). В runtime різниця між Data Binding та findViewById відсутня для більшості екранів. Для RecyclerView з тисячами елементів ViewBinding може бути швидшим через меншу кількість генерованого коду.

Як налагодити помилку Data Binding?

Data Binding компілює вирази в код на етапі збірки — помилки відображаються в Build Output як Compilation errors із зазначенням рядка XML. Типові помилки: невірний тип змінної, null-безпека (використовуйте ?? для значень за замовчуванням), відсутність імпорту класу. Увімкніть buildFeatures.dataBinding = true в build.gradle (app) та перевірте, що <layout> — кореневий тег XML. Для налагодження runtime-виразів використовуйте BindingConversion та логування в BindingAdapter.

Що таке BindingConversion?

@BindingConversion — анотація для статичних методів, що автоматично перетворюють типи у виразах Data Binding. Наприклад, конвертація Color Int в ColorDrawable: @BindingConversion fun colorToDrawable(color: Int): ColorDrawable = ColorDrawable(color). Після цього android:background="@{color.red}" працюватиме автоматично. BindingConversion глобальні — застосовуються до всіх Binding-виразів у проєкті.

Чи потрібно вимикати Data Binding для release-збірки?

Ні, Data Binding працює в release-збірці так само, як в debug. Оптимізація ProGuard/R8 може видалити Binding-класи, якщо вони не використовуються безпосередньо — додайте правило: -keep class * extends android.databinding.ViewDataBinding { *; }. Починаючи з Android Gradle Plugin 7.0, R8 коректно обробляє Data Binding без додаткових правил. Вимкнення Data Binding для release не дає приросту продуктивності, але ламає всі екрани, що його використовують.

Підсумки

  • Data Binding — бібліотека Jetpack для декларативного зв'язування XML з даними через вирази @{} та @={}.
  • Генерує type-safe Binding-класи, усуваючи findViewById() та ClassCastException.
  • ViewBinding — легка альтернатива без виразів; Data Binding обирайте для реактивних та складних UI.
  • Двостороннє зв'язування @={} — автоматична синхронізація View ↔ ViewModel для форм.
  • @BindingAdapter — кастомні атрибути (завантаження зображень, форматування, видимість).
  • Data Binding нативно підтримує LiveData та StateFlow через lifecycleOwner.
  • Data Binding скорочує UI-код на 30–50% та знижує кількість багів зв'язування до нуля.

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також