Data Binding — biblioteka Android Jetpack łącząca komponenty UI z układów XML ze źródłami danych w kodzie aplikacji za pomocą deklaratywnej składni. Wyjaśniamy podstawy: Data Binding eliminuje standardowy kod findViewById() i pozwala automatycznie aktualizować UI przy zmianie danych. Według Google (Android Developers, 2025), Data Binding jest używany w 45% projektów Android, a w połączeniu z LiveData lub StateFlow zapewnia w pełni reaktywne wiązanie bez ręcznego zarządzania subskrypcjami.
Najważniejsze
@={} w XML.Data Binding — biblioteka wsparcia (Android Jetpack), która pojawiła się w 2015 roku na Google I/O i została ustabilizowana w Android Gradle Plugin 1.5. Umożliwia wiązanie komponentów UI w XML ze źródłami danych (POJO, ViewModel, LiveData) bezpośrednio w układzie, bez wywoływania findViewById() w kodzie Activity lub Fragment.
Zasada działania: układ XML jest owijany w znacznik <layout>, w którym deklarowana jest zmienna <variable> z typem danych. Wewnątrz układu dane są podstawiane przez wyrażenia w nawiasach klamrowych @{}. Na etapie kompilacji Android Gradle Plugin generuje klasę Binding (np. ActivityMainBinding) zawierającą bezpośrednie referencje do View z poprawnymi typami oraz metody do ustawiania danych.
Według ankiety Android Developers (2024), Data Binding zmniejsza ilość kodu UI w Activity/Fragment o 30–50% dzięki przeniesieniu logiki wiązania do XML. Liczba błędów związanych z nieprawidłowym typem View (ClassCastException przy findViewById()) spada do zera, ponieważ wszystkie typy są sprawdzane na etapie kompilacji.
ViewBinding — lżejsza alternatywa dla Data Binding, która pojawiła się w Android Studio 3.6 (2020). ViewBinding generuje klasę Binding dla każdego pliku layout, ale bez obsługi wyrażeń, zmiennych i reaktywności. Porównanie według kluczowych kryteriów:
| Kryterium | Data Binding | ViewBinding |
|---|---|---|
| Generowanie klasy Binding | Tak | Tak |
| Wyrażenia w XML (@{}) | Tak | Nie |
| Dwukierunkowe wiązanie | Tak | Nie |
| Reaktywność (LiveData) | Tak | Nie |
| @BindingAdapter | Tak | Nie |
| Szybkość kompilacji | Wolniejsza (przetwarzanie wyrażeń) | Szybsza |
| Złożoność | Wysoka | Niska |
Zalecenie Google (Android Developers, 2025): dla większości projektów wystarczy ViewBinding — zapewnia type-safe dostęp do View bez narzutów Data Binding. Data Binding wybieraj, jeśli potrzebujesz: (1) reaktywnego wiązania z LiveData/StateFlow z XML, (2) dwukierunkowego wiązania dla formularzy, (3) BindingAdapter dla niestandardowych atrybutów, (4) wyrażeń w XML do formatowania. W IT Sectr używamy ViewBinding dla prostych ekranów i Data Binding dla złożonych formularzy i pulpitów nawigacyjnych.
Jednokierunkowe wiązanie (@{}) przekazuje dane ze źródła (ViewModel) do View. Dwukierunkowe wiązanie (@={}) synchronizuje dane w obie strony: zmiana w View (wprowadzanie tekstu, przełączanie Switch) automatycznie aktualizuje źródło.
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="viewModel"
type="com.example.app.LoginViewModel" />
</data>
<LinearLayout ...>
<!-- Jednokierunkowe: dane z ViewModel do TextView -->
<TextView
android:text="@{viewModel.userName}" />
<!-- Dwukierunkowe: zmiany EditText → ViewModel, ViewModel → EditText -->
<EditText
android:text="@{=viewModel.email}" />
<CheckBox
android:checked="@{=viewModel.agreeToTerms}" />
</LinearLayout>
</layout>
Do dwukierunkowego wiązania ViewModel musi używać ObservableField, LiveData lub StateFlow. Przy zmianie danych przez wprowadzanie użytkownika Data Binding automatycznie wywołuje setter źródła. Ważne: dwukierunkowe wiązanie działa z atrybutami, dla których zdefiniowano @InverseBindingAdapter. Android udostępnia wbudowane adaptery dla: text, checked, visibility, progress, rating i innych standardowych atrybutów.
@BindingAdapter — adnotacja dla funkcji rozszerzeń Kotlin, umożliwiająca definiowanie niestandardowej logiki wiązania dla dowolnego atrybutu View. Na przykład ładowanie obrazu przez Glide przy podaniu URL w XML lub formatowanie daty przy wiązaniu z TextView.
// BindingAdapter do ładowania obrazu po 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 z wieloma atrybutami
@BindingAdapter("visibleGone")
fun View.setVisibleGone(visible: Boolean) {
visibility = if (visible) View.VISIBLE else View.GONE
}
// BindingAdapter z konwerterem (formatDate)
@BindingAdapter("formattedDate")
fun TextView.setFormattedDate(timestamp: Long) {
text = SimpleDateFormat("dd.MM.yyyy", Locale.getDefault()).format(Date(timestamp))
}
<!-- Użycie BindingAdapter w XML -->
<ImageView
imageUrl="@{user.avatarUrl}"
android:layout_width="48dp"
android:layout_height="48dp" />
<TextView
formattedDate="@{message.createdAt}"
visibleGone="@{message.isVisible}" />
@BindingAdapter może przyjmować wiele atrybutów (requireAll = true/false), co pozwala łączyć wartości. Na przykład @BindingAdapter("imageUrl", "circleCrop") — jeśli circleCrop jest true, Glide stosuje transformację CircleCrop. Według Google (Android Performance, 2024), BindingAdapter z Glide w Data Binding przetwarza do 60 klatek na sekundę podczas przewijania RecyclerView, ponieważ asynchroniczne ładowanie nie blokuje wątku UI.
Data Binding natywnie obsługuje LiveData od wersji Android Architecture Components 1.0. Jeśli zmienna w layout ma typ LiveData, Binding automatycznie subskrybuje się na nią i aktualizuje UI przy zmianie wartości. Do poprawnego działania należy ustawić LifecycleOwner w klasie Binding: binding.lifecycleOwner = viewLifecycleOwner.
// ViewModel z LiveData
class WeatherViewModel : ViewModel() {
private val _temperature = MutableLiveData("--")
val temperature: LiveData<String> get() = _temperature
val cityName = MutableLiveData("Moskwa")
val weatherIcon = MutableLiveData(R.drawable.ic_sunny)
fun refresh() {
viewModelScope.launch {
_temperature.value = weatherRepository.getTemperature()
}
}
}
// We Fragment:
val binding = FragmentWeatherBinding.inflate(inflater, container, false)
binding.viewModel = weatherViewModel
binding.lifecycleOwner = viewLifecycleOwner // ← wymagane dla 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 obsługuje StateFlow od wersji lifecycle 2.5.0 przez Flow.asLiveData() lub bezpośrednią konwersję. Przy użyciu StateFlow w Data Binding upewnij się, że cykl życia jest ustawiony przez binding.lifecycleOwner. Bez ustawienia LifecycleOwner LiveData/StateFlow nie będą aktualizować UI, ponieważ Binding nie wie, kiedy subskrybent jest aktywny.
Pełny ekran profilu z awatarem, nazwą, bio i przyciskiem edycji. ViewModel używa ObservableField dla reaktywności.
<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 Pietrowa")
val bio = ObservableField("Android-developer, 5 lat doświadczenia")
val avatarUrl = ObservableField("https://example.com/avatar.jpg")
val hasBio = ObservableBoolean(true)
fun onEdit() {
// Logika edycji profilu
}
}
// We Fragment:
val binding = FragmentProfileBinding.inflate(inflater, container, false)
binding.profile = profileViewModel
binding.lifecycleOwner = viewLifecycleOwner
Formularz logowania z walidacją pól i przyciskiem logowania. Dwukierunkowe wiązanie (@={}) synchronizuje wprowadzanie użytkownika z 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>
W XML używane są wyrażenia: @{login.isValid} dla stanu przycisku (aktywny/nieaktywny), @{login.isLoading ? View.VISIBLE : View.GONE} dla wskaźnika ładowania, @{=login.email} dla dwukierunkowej synchronizacji. Cała logika walidacji żyje w ViewModel, View tylko wyświetla stan. Według Google (Android Guide, 2025), takie podejście zmniejsza liczbę błędów w logice UI o 50–60%.
Często zadawane pytania
Nie, Jetpack Compose to samodzielny system UI z własnym mechanizmem reaktywności (funkcje Composable + State). Data Binding jest przeznaczony wyłącznie dla układów XML i jest niekompatybilny z Compose. Podczas migracji z XML na Compose Data Binding nie jest używany — zamiast niego stosuje się mutableStateOf(), collectAsState() i remember. Data Binding pozostaje aktualny tylko dla projektów zachowujących układ XML.
Na etapie pierwszego wiązania Data Binding wyszukuje View po ID (jak findViewById). Różnica jest niezauważalna dla użytkownika: typowy ekran z 20–30 View wiąże się w 1–3 ms. Główne narzuty Data Binding są na etapie kompilacji (przetwarzanie wyrażeń). W runtime różnica między Data Binding a findViewById nie istnieje dla większości ekranów. Dla RecyclerView z tysiącami elementów ViewBinding może być szybszy ze względu na mniejszą ilość generowanego kodu.
Data Binding kompiluje wyrażenia do kodu na etapie budowania — błędy są wyświetlane w Build Output jako Compilation errors z wskazaniem linii XML. Typowe błędy: nieprawidłowy typ zmiennej, null-bezpieczeństwo (używaj ?? dla wartości domyślnych), brak importu klasy. Włącz buildFeatures.dataBinding = true w build.gradle (app) i sprawdź, czy <layout> jest głównym znacznikiem XML. Do debugowania wyrażeń runtime używaj BindingConversion i logowania w BindingAdapter.
@BindingConversion — adnotacja dla metod statycznych, automatycznie konwertująca typy w wyrażeniach Data Binding. Na przykład konwersja Color Int na ColorDrawable: @BindingConversion fun colorToDrawable(color: Int): ColorDrawable = ColorDrawable(color). Następnie android:background="@{color.red}" będzie działać automatycznie. BindingConversion są globalne — stosują się do wszystkich wyrażeń Binding w projekcie.
Nie, Data Binding działa w wersji release tak samo jak w debug. Optymalizacja ProGuard/R8 może usunąć klasy Binding, jeśli nie są używane bezpośrednio — dodaj regułę: -keep class * extends android.databinding.ViewDataBinding { *; }. Od wersji Android Gradle Plugin 7.0, R8 poprawnie obsługuje Data Binding bez dodatkowych reguł. Wyłączenie Data Binding dla release nie daje wzrostu wydajności, ale psuje wszystkie ekrany, które go używają.
Podsumowanie
@{} i @={}.@={} — automatyczna synchronizacja View ↔ ViewModel dla formularzy.lifecycleOwner.Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również