LiveData — یک محفظه داده قابل مشاهده از Android Jetpack است که چرخه حیات Activity، Fragment یا Service را در نظر میگیرد. بررسی میکنیم که LiveData چگونه اشتراکها را خودکار مدیریت میکند: مشترکین فعال بهروزرسانی دریافت میکنند و غیرفعالها نه، که نشت حافظه و کرشهای ناشی از ارجاعات منسوخ را از بین میبرد. طبق دادههای Google (Android Developers, 2025)، LiveData در 74% پروژههای Java و Kotlin به عنوان روش اصلی انتقال واکنشگرای داده از ViewModel به UI استفاده میشود.
نکات اصلی
LiveData — کلاسی از کتابخانه Android Jetpack است که الگوی Observer را با در نظر گرفتن چرخه حیات پیادهسازی میکند. برخلاف Observable یا Flow استاندارد، LiveData اشتراکها را خودکار مدیریت میکند: Observer فقط زمانی که LifecycleOwner در حالت فعال است (STARTED یا RESUMED) اعلان دریافت میکند. اگر مالک چرخه حیات به حالت غیرفعال (STOPPED یا DESTROYED) برود، اشتراک متوقف یا حذف میشود.
LiveData در Android Architecture Components (AAC) در سال ۲۰۱۷ در Google I/O همراه با ViewModel و Room معرفی شد. انگیزه اصلی از بین بردن مشکل نشت حافظه هنگام کار با دادههای ناهمگام بود: توسعهدهندگان اغلب فراموش میکردند از callbackها لغو اشتراک کنند که منجر به نگهداری ارجاع به Activityهای نابود شده میشد. LiveData لغو اشتراک را خودکار میکند — Observer متصل به LifecycleOwner پس از نابود شدن مالک، بهروزرسانی دریافت نمیکند.
طبق نظرسنجی Android Developers (2025)، هر دومین کرش قبل از پیادهسازی LiveData مربوط به فراخوانی متدها روی کنترلر UI نابود شده بود. LiveData این دسته از خطاها را کاملاً از بین میبرد. در IT Sectr ما LiveData را از سال ۲۰۱۸ در تمام پروژهها پیادهسازی کردیم — در ۷ سال حتی یک کرش به دلیل ارجاع منسوخ به Activity رخ نداده است.
تفاوت کلیدی LiveData با سایر محفظههای قابل مشاهده — اتصال به Lifecycle است. هنگام ایجاد مشاهدهگر، LiveData وضعیت LifecycleOwner را بررسی میکند: اگر وضعیت STARTED یا RESUMED باشد، Observer فعال در نظر گرفته میشود و بلافاصله بهروزرسانی دریافت میکند. اگر وضعیت PAUSED، STOPPED یا DESTROYED باشد، بهروزرسانیها تا بازگشت به حالت فعال تحویل داده نمیشوند.
مکانیزم از طریق کلاس LifecycleBoundObserver پیادهسازی میشود که با addObserver() در Lifecycle ثبت میشود. وقتی LifecycleOwner حالت را تغییر میدهد، callback onStateChanged() فراخوانی میشود و LiveData وضعیت فعالیت Observer را بهروز میکند. هنگام تنظیم داده از طریق setValue()، LiveData لیست مشاهدهگرها را مرور میکند و مقدار را فقط به مشاهدهگرهای فعال تحویل میدهد. وقتی مشاهدهگر به حالت DESTROYED میرود، Observer به طور خودکار از لیست مشترکین حذف میشود.
طبق مستندات Android Jetpack (2025)، مکانیزم LifecycleBoundObserver کمتر از ۰٫۵ میکروثانیه برای بررسی وضعیت مصرف میکند — سربار در مقایسه با عملیات معمول بهروزرسانی UI ناچیز است. این باعث میشود LiveData برای بهروزرسانیهای با فرکانس بالا (تایمرها، شمارندهها) بدون خطر کاهش عملکرد مناسب باشد.
MutableLiveData — وارث LiveData با متدهای عمومی setValue() و postValue() برای تغییر مقدار ذخیره شده. برخلاف LiveData، MutableLiveData برای نوشتن در دسترس است، اما در ViewModel مرسوم است که فقط LiveData (نسخه غیرقابل تغییر) منتشر شود و MutableLiveData زیر modificator 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() فقط از رشته اصلی (main thread) فراخوانی شود — بلافاصله به مشاهدهگرها اعلان میدهد. 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 builder (ظاهر شده در lifecycle-livedata-ktx 2.2.0) که امکان محاسبه ناهمگام مقدار LiveData در داخل کروتین را فراهم میکند. در داخل بلوک liveData { } زمینه suspend و همچنین تابع emit() برای انتشار مقادیر در دسترس است. تمام کروتینهای راهاندازی شده داخل builder به طور خودکار هنگام غیرفعال بودن همه مشاهدهگرها لغو میشوند.
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 در داخل کروتین). تایماوت: اگر هیچ Observer به مدت ۵ ثانیه (پیشفرض) فعال نباشد، کروتین لغو میشود. پس از فعالسازی مجدد، liveData { } از ابتدا اجرا میشود. طبق Google (Android Dev Summit 2024)، liveData builder ۴۰% کد الگویی را در مقایسه با مدیریت دستی 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 به طور خودکار بهروز میکند. این بدون کد اضافی کار میکند — فقط annotation @Query با نوع بازگشتی LiveData کافی است. در IT Sectr ما از Room + LiveData به عنوان پشته استاندارد برای ذخیره محلی داده در پروژههای Android از سال ۲۰۱۹ استفاده میکنیم.
سوالات متداول
LiveData — محفظه قابل مشاهده با پشتیبانی داخلی Lifecycle: Observer به طور خودکار فعال/غیرفعال میشود. StateFlow — جریان واکنشگرا از Kotlin Coroutines (Kotlinx Coroutines 1.3.7+)، غیرمتصل به Lifecycle، اما از آن از طریق stateIn(WhileSubscribed) پشتیبانی میکند. StateFlow نیاز به مدیریت صریح چرخه حیات در View دارد، اما به کروتینها، عملگرهای Flow و چندسکویی دسترسی میدهد. Google StateFlow را برای پروژههای جدید در Kotlin توصیه میکند و LiveData را برای کد Java یا هنگام نیاز به سازگاری با کتابخانههای قدیمی.
از تابع توسعهای liveData.asFlow() از کتابخانه lifecycle-livedata-ktx استفاده کنید. آن Flowای ایجاد میکند که مقدار فعلی LiveData را در هر تغییر منتشر میکند. سپس از طریق .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), initialValue) به StateFlow تبدیل کنید. تبدیل معکوس — stateFlow.asLiveData(). تبدیل متقابل امکان استفاده از مزایای هر دو کتابخانه را در یک پروژه فراهم میکند.
postValue() از AtomicReference برای ذخیره مقدار به تأخیر افتاده استفاده میکند. اگر postValue() دو بار قبل از پردازش توسط رشته اصلی فراخوانی شود، مقدار اول توسط دوم بازنویسی میشود — Observable فقط آخرین مقدار را دریافت میکند. این به دلیل نداشتن صف داخلی LiveData است: فقط یک مقدار به تأخیر افتاده ذخیره میکند. برای انتقال هر نقطه میانی (۱٪، ۲٪، … ۱۰۰٪) از 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 آخرین مقدار را هنگام انتقال FROM غیرفعال TO فعال دریافت میکند که مقداردهی اولیه صفحهها را سادهتر میکند.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.