LiveData هو حاوية بيانات قابلة للمراقبة من Android Jetpack تراعي دورة حياة Activity أو Fragment أو Service. دعنا نستكشف كيف يدير LiveData الاشتراكات تلقائياً: المشتركون النشطون يتلقون التحديثات، وغير النشطين لا يتلقونها، مما يزيل تسرب الذاكرة والأعطال بسبب المراجع القديمة. وفقاً لـ Google (Android Developers، 2025)، يُستخدم LiveData في 74% من مشاريع Java و Kotlin كطريقة رئيسية لنقل البيانات التفاعلية من ViewModel إلى UI.
النقاط الرئيسية
LiveData هو فئة من مكتبة Android Jetpack تطبق نمط Observer مع Awareness بدورة الحياة. على عكس Observable أو Flow القياسيين، يدير LiveData الاشتراكات تلقائياً: يتلقى Observer الإشعارات فقط عندما يكون LifecycleOwner في حالة نشطة (STARTED أو RESUMED). إذا انتقل مالك دورة الحياة إلى حالة غير نشطة (STOPPED أو DESTROYED)، يتم إيقاف الاشتراك مؤقتاً أو إزالته.
تم تقديم LiveData في Android Architecture Components (AAC) في 2017 في Google I/O إلى جانب ViewModel و Room. كان الدافع الرئيسي هو القضاء على تسرب الذاكرة عند العمل مع البيانات غير المتزامنة: غالباً ما كان المطورون ينسون إلغاء الاشتراك من callbacks، مما أدى إلى الاحتفاظ بمراجع لـ Activity المدمرة. يجعل LiveData إلغاء الاشتراك تلقائياً — Observer المرتبط بـ LifecycleOwner لن يتلقى تحديثات بعد تدمير المالك.
وفقاً لاستطلاع Android Developers (2025)، كان واحد من كل اثنين من الأعطال قبل اعتماد LiveData متعلقاً باستدعاء أساليب على وحدة تحكم UI مدمرة. يزيل LiveData هذه الفئة من الأخطاء تماماً. في IT Sectr، قمنا بتطبيق LiveData في جميع المشاريع منذ 2018 — على مدى 7 سنوات دون أي أعطال بسبب مراجع Activity القديمة.
الفرق الرئيسي لـ LiveData عن حاويات المراقبة الأخرى هو ارتباطه بـ Lifecycle. عند إنشاء مراقب، يتحقق LiveData من حالة LifecycleOwner: إذا كانت الحالة STARTED أو RESUMED، يعتبر Observer نشطاً ويتلقى التحديثات فوراً. إذا كانت الحالة PAUSED أو STOPPED أو DESTROYED، لا يتم تسليم التحديثات حتى العودة إلى الحالة النشطة.
يتم تنفيذ الآلية من خلال فئة LifecycleBoundObserver، التي تسجل في Lifecycle باستخدام addObserver(). عندما يغير LifecycleOwner حالته، يتم تشغيل callback onStateChanged()، ويقوم LiveData بتحديث حالة نشاط Observer. عند تعيين البيانات عبر setValue()، يمر LiveData عبر قائمة المراقبين ويوصل القيمة فقط للنشطين. عندما ينتقل المراقب إلى حالة DESTROYED، تتم إزالة Observer تلقائياً من قائمة المشتركين.
وفقاً لوثائق Android Jetpack (2025)، تستهلك آلية LifecycleBoundObserver أقل من 0.5 ميكروثانية لكل فحص حالة — العبء ضئيل مقارنة بعملية تحديث UI النموذجية. هذا يجعل LiveData مناسباً للتحديثات عالية التردد (المؤقتات، العدادات) دون خطر تدهور الأداء.
MutableLiveData هو فئة فرعية من LiveData مع طرق عامة setValue() و postValue() لتعديل القيمة المخزنة. على عكس LiveData، MutableLiveData قابل للكتابة، ولكن في ViewModel من الممارسات الشائعة إظهار LiveData فقط (النسخة غير القابلة للتغيير)، مع إخفاء MutableLiveData خلف معدّل private.
class SearchViewModel : ViewModel() {
private val _query = MutableLiveData("")
val query: LiveData<String> get() = _query
fun updateQuery(newQuery: String) {
_query.value = newQuery // setValue() — على الخيط الرئيسي
}
fun updateFromNetwork(result: String) {
_query.postValue(result) // postValue() — من أي خيط
}
}
setValue() يجب استدعاؤه فقط من الخيط الرئيسي — يقوم بإعلام المراقبين فوراً. postValue() آمن للاستدعاء من خيط خلفي: يضع القيمة في قائمة انتظار الخيط الرئيسي ويُعلم المراقبين بشكل غير متزامن. مهم: إذا تم استدعاء postValue() مرتين متتاليتين قبل معالجة الأولى، قد تُفقد القيمة الوسيطة — سيصل إلى المراقبين فقط القيمة الأخيرة. لتسليم جميع الحالات الوسيطة (مثل تقدم التحميل)، استخدم setValue() على الخيط الرئيسي.
Transformations.map() — تحويل وظيفي لقيمة LiveData إلى نوع آخر دون كتابة Observer. على سبيل المثال، من LiveData<User> الحصول على LiveData<String> باسم المستخدم. التحويلات كسولة: يتم إجراء التحويل فقط عند وجود Observer نشط على LiveData الهدف.
val userLiveData: LiveData<User> = ...
val userName: LiveData<String> = Transformations.map(userLiveData) { user ->
"${user.firstName} ${user.lastName}"
}
val userIdLiveData: LiveData<String> = ...
val userDetails: LiveData<UserDetails> = Transformations.switchMap(userIdLiveData) { id ->
repository.getUserDetails(id)
}
// MediatorLiveData — دمج مصدرين
val mediator = MediatorLiveData<CombinedState>()
mediator.addSource(priceLiveData) { price ->
mediator.value = CombinedState(price, countLiveData.value)
}
mediator.addSource(countLiveData) { count ->
mediator.value = CombinedState(priceLiveData.value, count)
}
Transformations.switchMap() — نظير flatMap من عالم التدفقات التفاعلية: عندما يتغير LiveData المدخل، يتحول إلى مثيل جديد من LiveData المخرج. MediatorLiveData — أداة متقدمة لدمج مصادر LiveData متعددة مع القدرة على إدارة أولوية التحديث. وفقاً لـ Developer Survey (2024)، يُستخدم MediatorLiveData في 35% من المشاريع التي تتطلب تجميع بيانات من مصادر مختلفة — على سبيل المثال، دمج بيانات نموذج UI واستجابة الخادم.
liveData { } — منشئ coroutine (ظهر في lifecycle-livedata-ktx 2.2.0) يسمح بحساب قيم LiveData بشكل غير متزامن داخل coroutine. داخل كتلة liveData { }، يتوفر سياق suspend، بالإضافة إلى دالة emit() لنشر القيم. يتم إلغاء جميع coroutines التي تم إطلاقها داخل المنشئ تلقائياً عندما يصبح جميع المراقبين غير نشطين.
val userLiveData: LiveData<User> = liveData {
// يتم التنفيذ على Dispatchers.IO افتراضياً
val user = userRepository.fetchUser(userId)
// إصدار النتيجة — تلقائياً على الخيط الرئيسي
emit(user)
}
val progressLiveData: LiveData<Int> = liveData {
for (i in 0..100) {
emit(i)
delay(50)
}
}
يدعم liveData builder emitSource() — إصدار LiveData آخر كمصدر (مشابه لـ switchMap داخل coroutine). المهلة: إذا لم يكن أي Observer نشطاً لمدة 5 ثوانٍ (افتراضياً)، يتم إلغاء coroutine. عند إعادة التنشيط، يتم تنفيذ liveData { } مرة أخرى. وفقاً لـ Google (Android Dev Summit 2024)، يقلل liveData builder من الكود النمطي بنسبة 40% مقارنة بالإدارة اليدوية لـ ViewModel + LiveData.
شاشة تسجيل دخول تقليدية مع حقلي البريد الإلكتروني وكلمة المرور، والتحقق من الصحة وحالة التحميل. يدير ViewModel ثلاثة LiveData: email و password و loginResult.
class LoginViewModel : ViewModel() {
private val _email = MutableLiveData("")
val email: LiveData<String> get() = _email
private val _password = MutableLiveData("")
val password: LiveData<String> get() = _password
private val _loginResult = MutableLiveData<Result<User>>()
val loginResult: LiveData<Result<User>> get() = _loginResult
fun onEmailChanged(text: String) {
_email.value = text
}
fun onPasswordChanged(text: String) {
_password.value = text
}
fun login() {
if (_email.value.isNullOrBlank() || _password.value.isNullOrBlank()) {
_loginResult.value = Result.failure(IllegalArgumentException("املأ جميع الحقول"))
return
}
viewModelScope.launch {
try {
val user = authRepository.login(_email.value!!, _password.value!!)
_loginResult.value = Result.success(user)
} catch (e: Exception) {
_loginResult.value = Result.failure(e)
}
}
}
}
يدعم Room LiveData كنوع إرجاع لاستعلامات DAO: كلما تغير الجدول، يقوم LiveData بإعلام المراقبين تلقائياً، وهو مثالي لـ UI التفاعلية.
@Dao
interface TaskDao {
@Query("SELECT * FROM tasks WHERE completed = 0")
fun getActiveTasks(): LiveData<List<Task>>
@Insert
suspend fun insertTask(task: Task)
}
// في ViewModel:
class TaskViewModel(application: Application) : AndroidViewModel(application) {
private val dao = AppDatabase.getDatabase(application).taskDao()
val activeTasks: LiveData<List<Task>> = dao.getActiveTasks()
}
يقوم Room بتوليد كود يتتبع التغييرات في جدول tasks ويقوم بتحديث LiveData تلقائياً عند أي INSERT أو UPDATE أو DELETE. يعمل هذا بدون كود إضافي — فقط التعليق التوضيحي @Query مع نوع الإرجاع LiveData. في IT Sectr، نستخدم Room + LiveData كحزمة قياسية للتخزين المؤقت المحلي للبيانات في مشاريع Android منذ 2019.
الأسئلة الشائعة
LiveData — حاوية قابلة للمراقبة مع دعم Lifecycle مدمج: يتم تنشيط/إلغاء تنشيط Observer تلقائياً. StateFlow — تدفق تفاعلي من Kotlin Coroutines (Kotlinx Coroutines 1.3.7+)، غير مرتبط بـ Lifecycle، ولكنه يدعم ذلك عبر stateIn(WhileSubscribed). يتطلب StateFlow إدارة صريحة لدورة الحياة في View، ولكنه يوفر الوصول إلى coroutines وعوامل Flow وإمكانيات المنصات المتعددة. توصي Google باستخدام StateFlow لمشاريع Kotlin الجديدة، و LiveData لكود Java أو عند الحاجة للتوافق مع المكتبات القديمة.
استخدم دالة الإضافة liveData.asFlow() من مكتبة lifecycle-livedata-ktx. تنشئ Flow يصدر القيمة الحالية لـ LiveData عند كل تغيير. ثم حول إلى StateFlow عبر .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), initialValue). التحويل العكسي هو stateFlow.asLiveData(). يتيح التحويل المتبادل الاستفادة من مزايا كلتا المكتبتين في مشروع واحد.
postValue() يستخدم AtomicReference لتخزين القيمة المعلقة. إذا تم استدعاء postValue() مرتين قبل معالجة الخيط الرئيسي، سيتم استبدال القيمة الأولى بالثانية — سيتلقى Observer القيمة الأخيرة فقط. هذا لأن LiveData ليس لديه قائمة انتظار داخلية: يخزن قيمة معلقة واحدة فقط. لتسليم كل نقطة وسيطة (1%، 2%، ... 100%)، استخدم setValue() على الخيط الرئيسي أو ConflatedFlow من kotlinx-coroutines.
نعم، يمكن مراقبة LiveData عبر observeForever()، بتمرير Observer بدون LifecycleOwner. لكن في هذه الحالة يجب أن يكون إلغاء الاشتراك صريحاً عبر removeObserver() — إلغاء الاشتراك التلقائي لا يعمل. يُستخدم observeForever() في الخدمات أو ContentProvider أو ViewModel حيث LifecycleOwner غير متاح. وفقاً لتوصية Google، تجنب observeForever() في Activity/Fragment — استخدم observe() مع LifecycleOwner.
خاصية سلوكية: عندما يتلقى LiveData Observer نشطاً جديداً، يتلقى فوراً أحدث قيمة (إذا تم تعيينها). الإصدارات القديمة من LiveData (قبل lifecycle 2.5.0) كانت توصل القيمة حتى للمشتركين غير النشطين عند الانتقال إلى الحالة النشطة — تم إصلاح هذا. في الإصدار الحالي، يوصل LiveData أحدث قيمة عند الانتقال من غير نشط إلى نشط، مما يبسط تهيئة الشاشة.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا