Zjistěte, co je Two-Way Binding — obousměrné vázání dat, které automaticky synchronizuje model a zobrazení v mobilních aplikacích. Na rozdíl od ruční aktualizace UI pomocí findViewById, mechanismus vázání aktualizuje model při změně vstupu uživatele a zobrazení při změně dat. Podle Google I/O 2024 snižuje vázání šablonový UI kód o 30–50% v projektech Android a iOS. Tento přístup se uplatňuje v frameworkách — od Jetpack Compose a SwiftUI po Flutter a React Native.
Hlavní body
Two-Way Binding (obousměrné vázání dat) — architektonický mechanismus, při kterém se změny v datovém modelu automaticky projeví v uživatelském rozhraní a změny v UI okamžitě aktualizují model. Na rozdíl od jednosměrného vázání, kde tok dat jde pouze od modelu k zobrazení, obousměrné vázání vytváří uzavřený synchronizační cyklus bez ručního kódování každé aktualizace.
Podle Android Developers Blog (2023) je knihovna DataBinding, zavedená v roce 2015, používána ve 42% komerčních Android aplikací. Mechanismus je obzvláště žádaný ve vstupních formulářích — textová pole, přepínače, posuvníky a zaškrtávací políčka — kde se vstup uživatele musí okamžitě projevit v modelu a programové změny v UI. Ve všech těchto scénářích programátor napíše jedno vázání místo dvojice „posluchač + setter”.
V IT Sectr jsme používali obousměrné vázání v projektech od roku 2017 a doporučujeme ho používat vědomě: pro jednoduchá vstupní pole, ale ne pro složité stavy se závislostmi.
Mechanismus Two-Way Binding je postaven na třech klíčových prvcích: pozorovatelné pole (observable), posluchač změn (listener) a mechanismus zpětné synchronizace. Když uživatel zadá text do pole EditText, systém zachytí událost TextWatcher, zapíše novou hodnotu do vázané proměnné a upozorní UI na nutnost překreslení, pokud se proměnná změnila z kódu.
Pod kapotou generuje knihovna DataBinding v Android třídu Binding ve fázi kompilace, která obsahuje veškerou logiku vázání. Pro každé View s atributem @={variable} vzniká pár setter + getter s invalidací. V SwiftUI vykonává podobnou práci propertyWrapper @Binding, který synchronizuje hodnotu prostřednictvím mechanismu Combine. SwiftUI sleduje změny pomocí vlastností @Published a automaticky překresluje View při každé změně vázané proměnné.
Podle WWDC Session 10033 (2023) zpracovává mechanismus @Binding v SwiftUI až 60 snímků za sekundu při synchronizaci vstupních polí, což ho činí vhodným pro interaktivní formuláře bez zpoždění. V obou frameworkách je Two-Way Binding syntaktický cukr nad vzorem Observer, automatizující přihlášení k odběru a oznamování.
V Android je obousměrné vázání k dispozici ve dvou variantách: klasický XML-DataBinding pomocí atributu @={} a Jetpack Compose pomocí obousměrných odkazů na stav. Oba přístupy řeší stejný úkol — synchronizaci UI a modelu — ale liší se syntaxí a oblastí použití.
V XML značení se obousměrné vázání označuje syntaxí @={variable.property} — rovnítko uvnitř složených závorek ho odlišuje od jednosměrného @{variable}. Pro vlastní View je vyžadována anotace @BindingAdapter s uvedením atributu inverse.
<layout>
<data>
<variable name="viewModel" type="com.example.LoginViewModel" />
</data>
<EditText
android:text="@{viewModel.email}" />
<CheckBox
android:checked="@{viewModel.agreeToTerms}" />
</layout>Příklad ukazuje nejjednodušší formulář s emailem a zaškrtávacím políčkem — obě pole používají obousměrné vázání, což eliminuje potřebu psát TextWatcher a OnCheckedChangeListener v kódu Activity. Při změně textu uživatelem se pole viewModel.email automaticky aktualizuje.
@BindingAdapter("app:rating")
fun RatingBar.setRating(rating: Float) {
if (rating != this.rating) {
this.rating = rating
}
}
@InverseBindingAdapter("app:rating")
fun RatingBar.getRating(): Float = this.rating
@BindingAdapter("app:ratingAttrChanged")
fun RatingBar.setListeners(
listener: InverseBindingListener?
) {
this.onRatingBarChangeListener =
RatingBar.OnRatingBarChangeListener { _, _, _ -> listener?.onChange() }
}Vlastní BindingAdapter pro RatingBar používá dvojici anotací — @BindingAdapter a @InverseBindingAdapter — aby knihovna DataBinding věděla, jak číst hodnotu z View (zpětná vazba) a jak zapisovat do View (přímé vázání). Třetí adaptér s příponou AttrChanged upozorňuje systém na změnu hodnoty uživatelem.
Jetpack Compose nepodporuje syntaxi @={}, ale poskytuje podobný mechanismus prostřednictvím mutableStateOf a explicitního předání funkce setter. Obousměrné vázání v Compose je založeno na předání State a callback funkce (value, onValueChange) podřízeným komponentám.
@Composable
fun LoginScreen() {
var email by remember { mutableStateOf("") }
OutlinedTextField(
value = email,
onValueChange = { email = it },
label = { Text("Email") }
)
}
@Composable
fun CustomRatingBar(
rating: Float,
onRatingChange: (Float) -> Unit
) {
Slider(
value = rating,
onValueChange = onRatingChange,
valueRange = 0f..5f
)
}V Compose je obousměrné vázání simulováno pomocí páru state + callback — rodič předá aktuální hodnotu a funkci pro její aktualizaci, podřízená komponenta zavolá callback při interakci uživatele. Tento přístup explicitně ukazuje směr toku dat, což zjednodušuje ladění ve srovnání s implicitní synchronizací DataBinding.
V SwiftUI je obousměrné vázání realizováno pomocí propertyWrapper @Binding, který vytváří read-write odkaz na zdroj dat patřící nadřazenému View. @Binding neukládá hodnotu samostatně — čte a zapisuje prostřednictvím @State nebo @StateObject rodiče.
struct LoginView: View {
@State private var email = ""
@State private var agreeToTerms = false
var body: some View {
Form {
TextField("Email", text: $email)
Toggle("Souhlasím s podmínkami", isOn: $agreeToTerms)
ChildRatingView(rating: $rating)
}
}
}
struct ChildRatingView: View {
@Binding var rating: Double
var body: some View {
Slider(value: $rating, in: 0...5)
}
}Symbol $ před názvem proměnné vytváří odkaz Binding: $email má typ Binding<String>, nikoli String. SwiftUI automaticky propojuje změnu textu v TextField s aktualizací vlastnosti email prostřednictvím mechanismu Combine. Nadřazené View předá podřízené komponentě Binding na svůj @State, což umožňuje změnu stavu z libovolné úrovně hierarchie bez delegátů nebo callbacků.
Podle Apple WWDC 2023 používá SwiftUI diffing algoritmus k minimalizaci překreslování: pokud se hodnota @Binding změnila, ale View na této hodnotě nezávisí, k překreslení nedojde. To zajišťuje výkon srovnatelný s UIKit (až 120 FPS na displejích ProMotion).
Volba mezi obousměrným vázáním a jednosměrným tokem dat (UDF) je jedním z klíčových architektonických rozhodnutí v mobilním vývoji. Two-Way Binding je optimální pro lokální stavy formuláře, kde se každý krok uživatele musí okamžitě projevit v modelu bez dalšího kódu. UDF je preferován pro globální stav aplikace, kde je předvídatelnost změn důležitější než rychlost vývoje.
| Kritérium | Two-Way Binding | UDF |
|---|---|---|
| Objem kódu ve formuláři | 1 řádek (atribut @={}) | 5–7 řádků (State, Intent, Reducer) |
| Ladění toku dat | Obtížné (kdo změnil — UI nebo kód?) | Snadné (všechny změny přes Intent) |
| Výkon | Vysoký (nativní synchronizace) | Střední (vrstva Reducer + Redux) |
| Škálovatelnost | Klesá u složitých formulářů s validací | Roste s počtem obrazovek |
| Předvídatelnost stavů | Nízká (vedlejší účinky cyklů) | Vysoká (reducer — jediný zdroj pravdy) |
Doporučení: používejte Two-Way Binding pro jednoduchá vstupní pole (text, zaškrtávací políčka, přepínače) ve formulářích se 3–5 poli bez složité validace. Pro obrazovky s globálním stavem, síťovými požadavky a závislými poli aplikujte UDF s jednosměrným tokem a explicitním zpracováním událostí. V IT Sectr kombinujeme oba přístupy: Two-Way Binding uvnitř formuláře, UDF pro navigaci a obchodní logiku.
Nekonečný cyklus aktualizace — nejčastější problém při používání Two-Way Binding. Cyklus vzniká, když změna modelu způsobí aktualizaci UI, která znovu změní model. V DataBinding k tomu dochází, pokud getter v @InverseBindingAdapter vrátí novou hodnotu ihned po volání setter. Řešení — zkontrolujte, zda se hodnota změnila před zpětným zápisem (podmínka guard).
Druhá častá chyba — vázání vypočítávaných polí. Pokud pole závisí na jiném poli (např. celková cena = cena × množství), obousměrné vázání může vést k nekonzistentnímu stavu. Například uživatel změní množství, spustí se přepočet ceny, který znovu změní množství. Pro vypočítávaná pole používejte jednosměrné vázání s Flow nebo Combine.
Třetí chyba — vázání Observable polí bez LifecycleOwner. V Android DataBinding je třeba předat LifecycleOwner do bindingu, jinak pozorovatelé nebudou vyčištěni při zničení Activity, což vede k úniku paměti. Vždy předávejte viewLifecycleOwner ve fragmentech a this v Activity.
Podle Google Issue Tracker (2024) je přibližně 15% hlášení chyb týkajících se DataBinding spojeno s cyklickými aktualizacemi. Pro diagnostiku používejte Android Studio Layout Inspector — zobrazuje aktuální hodnoty všech bindingů na obrazovce, což zjednodušuje nalezení zdroje nekonečného cyklu.
Často kladené otázky
Jednosměrné vázání (One-Way Binding) přenáší data pouze od modelu k zobrazení — při změně modelu se UI aktualizuje, ale vstup uživatele model přímo nemění. Two-Way Binding synchronizuje data v obou směrech: změna v UI automaticky aktualizuje model a naopak. V syntaxi DataBinding je rozdíl označen symboly @{} (One-Way) a @={} (Two-Way).
Nepoužívejte obousměrné vázání pro složité formuláře se závislými poli, vypočítávanými hodnotami nebo vlastní validací — v těchto scénářích se tok dat stává nepředvídatelným. Vyhněte se mu také v seznamech RecyclerView s velkým počtem prvků, kde každý prvek má binding: výkon klesá kvůli mnoha pozorovatelům. UDF s jednosměrným tokem a zpracováním událostí přes Intent se lépe škáluje.
Jetpack Compose nemá vestavěnou syntaxi @={}, ale obousměrná synchronizace je realizována pomocí páru State + callback (onValueChange). Rodič předá aktuální hodnotu (State) a funkci aktualizace, podřízená komponenta zavolá callback při změně. Jedná se o explicitní, nikoli implicitní vázání — tok dat zůstává viditelný a sledovatelný.
Pro ladění cyklů v DataBinding použijte Android Studio Layout Inspector — zobrazuje aktuální hodnoty všech vázaných proměnných na obrazovce. Přidejte logování do @InverseBindingAdapter a zkontrolujte, zda getter nevrací hodnotu odlišnou od právě zapsané. Standardní řešení — podmínka guard: if (newValue != currentValue) před zpětným zápisem.
Ve Flutter není vestavěné obousměrné vázání, ale je emulováno pomocí kombinace TextEditingController a callbacku onChanged. Pro StatefulWidget se programátor ručně přihlásí k odběru změn kontroléru a aktualizuje model. V Provider a Riverpod je obousměrná synchronizace postavena přes Selector, který přestaví widget při změně modelu a zavolá callback při vstupu uživatele.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také