Data Binding — مكتبة Android Jetpack التي تربط مكونات واجهة المستخدم من تخطيطات XML بمصادر البيانات في كود التطبيق من خلال صيغة تعريفية. نشرح الأساسيات: Data Binding يلغي كود findViewById() المتكرر ويسمح بتحديث واجهة المستخدم تلقائيًا عند تغير البيانات. وفقًا لـ Google (Android Developers، 2025)، يُستخدم Data Binding في 45% من مشاريع Android، وبالاقتران مع LiveData أو StateFlow يوفر ربطًا تفاعليًا بالكامل دون إدارة يدوية للاشتراكات.
النقاط الرئيسية
@={} في XML.Data Binding هي مكتبة دعم (Android Jetpack) ظهرت لأول مرة في 2015 في Google I/O وتم تثبيتها في Android Gradle Plugin 1.5. تسمح بربط مكونات واجهة المستخدم في XML بمصادر البيانات (POJO، ViewModel، LiveData) مباشرة في التخطيط، دون استدعاء findViewById() في كود Activity أو Fragment.
مبدأ العمل: يتم تغليف تخطيط XML في علامة <layout>، والتي تعلن عن <variable> بنوع بيانات. داخل التخطيط، يتم إدراج البيانات من خلال تعبيرات بين الأقواس المتعرجة @{}. في وقت الترجمة، يقوم Android Gradle Plugin بإنشاء كلاس Binding (مثل ActivityMainBinding) يحتوي على مراجع مباشرة للـ Views بأنواع صحيحة وطرق لتعيين البيانات.
وفقًا لاستطلاع Android Developers (2024)، يقلل Data Binding من حجم كود واجهة المستخدم في Activity/Fragment بنسبة 30–50% عن طريق نقل منطق الربط إلى XML. عدد الأخطاء المتعلقة بأنواع View غير الصحيحة (ClassCastException مع findViewById()) ينخفض إلى الصفر، حيث يتم التحقق من جميع الأنواع في وقت الترجمة.
ViewBinding هو بديل أخف لـ Data Binding، تم تقديمه في Android Studio 3.6 (2020). ViewBinding ينشئ كلاس Binding لكل ملف تخطيط، ولكن بدون دعم للتعبيرات والمتغيرات والتفاعلية. مقارنة حسب المعايير الرئيسية:
| المعيار | Data Binding | ViewBinding |
|---|---|---|
| إنشاء كلاس Binding | نعم | نعم |
| التعبيرات في XML (@{}) | نعم | لا |
| الربط ثنائي الاتجاه | نعم | لا |
| تفاعلي (LiveData) | نعم | لا |
| @BindingAdapter | نعم | لا |
| سرعة الترجمة | أبطأ (معالجة التعبيرات) | أسرع |
| التعقيد | عالٍ | منخفض |
توصية Google (Android Developers، 2025): لمعظم المشاريع، ViewBinding كافٍ — فهو يوفر وصولًا type-safe إلى Views دون عبء 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 يسمح بتعريف منطق ربط مخصص لأي خاصية 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 صحيحًا، يطبق Glide تحويل CircleCrop. وفقًا لـ Google (Android Performance، 2024)، يعالج BindingAdapter مع Glide في Data Binding ما يصل إلى 60 إطارًا في الثانية عند تمرير RecyclerView، حيث أن التحميل غير المتزامن لا يحظر خيط واجهة المستخدم.
Data Binding يدعم LiveData بشكل أصلي منذ Android Architecture Components 1.0. إذا كان المتغير في التخطيط من نوع LiveData، فإن Binding يشترك تلقائيًا فيه ويحدث واجهة المستخدم عندما تتغير القيمة. للتشغيل الصحيح، يجب تعيين 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 بتحديث واجهة المستخدم لأن 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)، يقلل هذا النهج من عدد أخطاء منطق واجهة المستخدم بنسبة 50–60%.
الأسئلة المتداولة
لا، Jetpack Compose هو نظام واجهة مستخدم مستقل بآلية التفاعل الخاصة به (دوال Composable + State). Data Binding مصمم حصريًا لتخطيطات XML وغير متوافق مع Compose. عند الترحيل من XML إلى Compose، لا يُستخدم Data Binding — بدلاً من ذلك يتم تطبيق mutableStateOf() و collectAsState() و remember. يظل Data Binding مناسبًا فقط للمشاريع التي تحتفظ بتخطيطات XML.
في مرحلة الربط الأولى، يقوم Data Binding بالبحث عن View بواسطة المعرف (مثل findViewById). الفرق غير محسوس للمستخدم: شاشة نموذجية بـ 20–30 View يتم ربطها في 1–3 مللي ثانية. العبء الرئيسي لـ Data Binding يكون في وقت الترجمة (معالجة التعبيرات). في وقت التشغيل، لا يوجد فرق بين Data Binding و findViewById لمعظم الشاشات. بالنسبة لـ RecyclerView بآلاف العناصر، قد يكون ViewBinding أسرع بسبب كمية الكود الأقل المولدة.
يقوم Data Binding بترجمة التعبيرات إلى كود في وقت البناء — تظهر الأخطاء في Build Output كـ Compilation errors مع الإشارة إلى سطر XML. الأخطاء النموذجية: نوع متغير غير صحيح، مشكلات أمان القيمة الخالية (استخدم ?? للقيم الافتراضية)، عدم استيراد الكلاس. فعّل buildFeatures.dataBinding = true في build.gradle (app) وتحقق من أن <layout> هو العلامة الجذرية لـ XML. لتصحيح التعبيرات في وقت التشغيل، استخدم BindingConversion والتسجيل في BindingAdapter.
@BindingConversion هو تعليق توضيحي للطرق الثابتة التي تحول الأنواع تلقائيًا في تعبيرات Data Binding. على سبيل المثال، تحويل Color Int إلى ColorDrawable: @BindingConversion fun colorToDrawable(color: Int): ColorDrawable = ColorDrawable(color). بعد ذلك، سيعمل android:background="@{color.red}" تلقائيًا. BindingConversions عامة — يتم تطبيقها على جميع تعبيرات 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. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا