LiveData là một vùng chứa dữ liệu có thể quan sát từ Android Jetpack, tôn trọng vòng đời của Activity, Fragment hoặc Service. Hãy khám phá cách LiveData tự động quản lý đăng ký: người đăng ký hoạt động nhận được cập nhật, người không hoạt động thì không, giúp loại bỏ rò rỉ bộ nhớ và sự cố do tham chiếu cũ. Theo Google (Android Developers, 2025), LiveData được sử dụng trong 74% dự án Java và Kotlin như cách chính để truyền dữ liệu phản ứng từ ViewModel đến UI.
Những điểm chính
LiveData là một lớp từ thư viện Android Jetpack triển khai mẫu Observer với nhận thức về vòng đời. Không giống như Observable hoặc Flow tiêu chuẩn, LiveData tự động quản lý đăng ký: Observer chỉ nhận thông báo khi LifecycleOwner ở trạng thái hoạt động (STARTED hoặc RESUMED). Nếu chủ sở hữu vòng đời chuyển sang trạng thái không hoạt động (STOPPED hoặc DESTROYED), đăng ký sẽ bị tạm dừng hoặc bị xóa.
LiveData được giới thiệu trong Android Architecture Components (AAC) vào năm 2017 tại Google I/O cùng với ViewModel và Room. Động lực chính là loại bỏ rò rỉ bộ nhớ khi làm việc với dữ liệu bất đồng bộ: các nhà phát triển thường quên hủy đăng ký khỏi các callback, dẫn đến giữ tham chiếu đến các Activity đã bị hủy. LiveData làm cho việc hủy đăng ký trở nên tự động — Observer được liên kết với LifecycleOwner sẽ không nhận được cập nhật sau khi chủ sở hữu bị hủy.
Theo khảo sát Android Developers (2025), cứ hai sự cố thì có một trước khi áp dụng LiveData liên quan đến việc gọi phương thức trên bộ điều khiển UI đã bị hủy. LiveData loại bỏ hoàn toàn loại lỗi này. Tại IT Sectr, chúng tôi đã triển khai LiveData trong tất cả các dự án từ năm 2018 — hơn 7 năm không có sự cố nào do tham chiếu Activity cũ.
Sự khác biệt chính của LiveData so với các vùng chứa có thể quan sát khác là sự ràng buộc với Lifecycle. Khi một người quan sát được tạo, LiveData kiểm tra trạng thái của LifecycleOwner: nếu trạng thái là STARTED hoặc RESUMED, Observer được coi là hoạt động và nhận cập nhật ngay lập tức. Nếu trạng thái là PAUSED, STOPPED hoặc DESTROYED, các cập nhật sẽ không được gửi cho đến khi quay lại trạng thái hoạt động.
Cơ chế được triển khai thông qua lớp LifecycleBoundObserver, đăng ký trong Lifecycle bằng addObserver(). Khi LifecycleOwner thay đổi trạng thái, callback onStateChanged() được kích hoạt và LiveData cập nhật trạng thái hoạt động của Observer. Khi dữ liệu được thiết lập qua setValue(), LiveData duyệt qua danh sách người quan sát và chỉ gửi giá trị cho những người đang hoạt động. Khi người quan sát chuyển sang trạng thái DESTROYED, Observer sẽ tự động bị xóa khỏi danh sách người đăng ký.
Theo tài liệu Android Jetpack (2025), cơ chế LifecycleBoundObserver tiêu thụ ít hơn 0,5 µs cho mỗi lần kiểm tra trạng thái — chi phí không đáng kể so với một thao tác cập nhật UI điển hình. Điều này làm cho LiveData phù hợp với các bản cập nhật tần suất cao (bộ đếm thời gian, bộ đếm) mà không có nguy cơ suy giảm hiệu suất.
MutableLiveData là một lớp con của LiveData với các phương thức công khai setValue() và postValue() để sửa đổi giá trị được lưu trữ. Không giống như LiveData, MutableLiveData có thể ghi, nhưng trong ViewModel, thông thường chỉ nên hiển thị LiveData (phiên bản bất biến), ẩn MutableLiveData sau bộ sửa đổi private.
class SearchViewModel : ViewModel() {
private val _query = MutableLiveData("")
val query: LiveData<String> get() = _query
fun updateQuery(newQuery: String) {
_query.value = newQuery // setValue() — trên luồng chính
}
fun updateFromNetwork(result: String) {
_query.postValue(result) // postValue() — từ bất kỳ luồng nào
}
}
setValue() chỉ được gọi từ luồng chính — nó thông báo ngay lập tức cho người quan sát. postValue() an toàn để gọi từ luồng nền: nó đặt giá trị vào hàng đợi của luồng chính và thông báo cho người quan sát một cách bất đồng bộ. Quan trọng: nếu postValue() được gọi hai lần liên tiếp trước khi xử lý lần đầu, giá trị trung gian có thể bị mất — chỉ giá trị cuối cùng đến được người quan sát. Để gửi tất cả các trạng thái trung gian (ví dụ: tiến trình tải), hãy sử dụng setValue() trên luồng chính.
Transformations.map() — một phép biến đổi chức năng của giá trị LiveData này sang kiểu khác mà không cần viết Observer. Ví dụ: từ LiveData<User> lấy LiveData<String> với tên người dùng. Các phép biến đổi là lười biếng: phép biến đổi chỉ được thực hiện khi có Observer hoạt động trên LiveData đích.
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 — hợp nhất hai nguồn
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() — tương tự như flatMap từ thế giới luồng phản ứng: khi LiveData đầu vào thay đổi, nó chuyển sang một phiên bản mới của LiveData đầu ra. MediatorLiveData — một công cụ nâng cao để hợp nhất nhiều nguồn LiveData với khả năng quản lý ưu tiên cập nhật. Theo Developer Survey (2024), MediatorLiveData được sử dụng trong 35% dự án yêu cầu tổng hợp dữ liệu từ các nguồn khác nhau — ví dụ: kết hợp dữ liệu biểu mẫu UI và phản hồi máy chủ.
liveData { } — một trình xây dựng coroutine (được giới thiệu trong lifecycle-livedata-ktx 2.2.0) cho phép tính toán giá trị LiveData không đồng bộ bên trong coroutine. Bên trong khối liveData { }, có sẵn ngữ cảnh suspend, cũng như hàm emit() để xuất bản giá trị. Tất cả các coroutine được khởi chạy bên trong trình xây dựng sẽ tự động bị hủy khi tất cả người quan sát trở nên không hoạt động.
val userLiveData: LiveData<User> = liveData {
// Thực thi trên Dispatchers.IO theo mặc định
val user = userRepository.fetchUser(userId)
// Phát ra kết quả — tự động trên luồng chính
emit(user)
}
val progressLiveData: LiveData<Int> = liveData {
for (i in 0..100) {
emit(i)
delay(50)
}
}
liveData builder hỗ trợ emitSource() — phát ra một LiveData khác làm nguồn (tương tự switchMap bên trong coroutine). Thời gian chờ: nếu không có Observer nào hoạt động trong 5 giây (mặc định), coroutine sẽ bị hủy. Khi được kích hoạt lại, liveData { } thực thi lại. Theo Google (Android Dev Summit 2024), liveData builder giảm 40% mã soạn sẵn so với quản lý thủ công ViewModel + LiveData.
Màn hình đăng nhập cổ điển với các trường email và mật khẩu, xác thực và trạng thái tải. ViewModel quản lý ba LiveData: email, password và 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("Điền tất cả các trường"))
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 hỗ trợ LiveData làm kiểu trả về cho truy vấn DAO: mỗi khi bảng thay đổi, LiveData tự động thông báo cho người quan sát, lý tưởng cho UI phản ứng.
@Dao
interface TaskDao {
@Query("SELECT * FROM tasks WHERE completed = 0")
fun getActiveTasks(): LiveData<List<Task>>
@Insert
suspend fun insertTask(task: Task)
}
// Trong ViewModel:
class TaskViewModel(application: Application) : AndroidViewModel(application) {
private val dao = AppDatabase.getDatabase(application).taskDao()
val activeTasks: LiveData<List<Task>> = dao.getActiveTasks()
}
Room tạo mã theo dõi các thay đổi trong bảng tasks và tự động cập nhật LiveData khi có INSERT, UPDATE hoặc DELETE. Điều này hoạt động mà không cần mã bổ sung — chỉ cần chú thích @Query với kiểu trả về LiveData. Tại IT Sectr, chúng tôi đã sử dụng Room + LiveData làm ngăn xếp tiêu chuẩn cho bộ nhớ đệm dữ liệu cục bộ trong các dự án Android từ năm 2019.
Câu hỏi thường gặp
LiveData là vùng chứa có thể quan sát với hỗ trợ Lifecycle tích hợp: Observer tự động được kích hoạt/hủy kích hoạt. StateFlow là luồng phản ứng từ Kotlin Coroutines (Kotlinx Coroutines 1.3.7+), không gắn với Lifecycle, nhưng hỗ trợ điều này qua stateIn(WhileSubscribed). StateFlow yêu cầu quản lý vòng đời rõ ràng trong View, nhưng cung cấp quyền truy cập vào coroutines, toán tử Flow và khả năng đa nền tảng. Google khuyến nghị StateFlow cho các dự án Kotlin mới, LiveData cho mã Java hoặc khi cần tương thích với các thư viện cũ.
Sử dụng hàm mở rộng liveData.asFlow() từ thư viện lifecycle-livedata-ktx. Nó tạo một Flow phát ra giá trị LiveData hiện tại tại mỗi thay đổi. Sau đó chuyển đổi thành StateFlow qua .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), initialValue). Chuyển đổi ngược lại là stateFlow.asLiveData(). Chuyển đổi qua lại cho phép tận dụng lợi thế của cả hai thư viện trong một dự án.
postValue() sử dụng AtomicReference để lưu trữ giá trị đang chờ xử lý. Nếu postValue() được gọi hai lần trước khi luồng chính xử lý, giá trị đầu tiên sẽ bị ghi đè bởi giá trị thứ hai — Observer chỉ nhận được giá trị cuối cùng. Điều này là do LiveData không có hàng đợi nội bộ: nó chỉ lưu trữ một giá trị đang chờ xử lý. Để gửi từng điểm trung gian (1%, 2%, … 100%), hãy sử dụng setValue() trên luồng chính hoặc ConflatedFlow từ kotlinx-coroutines.
Có, LiveData có thể được quan sát qua observeForever(), truyền Observer mà không có LifecycleOwner. Tuy nhiên, trong trường hợp này, việc hủy đăng ký phải rõ ràng qua removeObserver() — hủy đăng ký tự động không hoạt động. observeForever() được sử dụng trong các dịch vụ, ContentProvider hoặc ViewModel nơi LifecycleOwner không khả dụng. Theo khuyến nghị của Google, tránh observeForever() trong Activity/Fragment — hãy sử dụng observe() với LifecycleOwner.
Đặc điểm hành vi: khi LiveData nhận được một Observer hoạt động mới, nó sẽ ngay lập tức nhận được giá trị mới nhất (nếu đã được đặt). Các phiên bản LiveData cũ hơn (trước lifecycle 2.5.0) gửi giá trị ngay cả cho người đăng ký không hoạt động khi chuyển sang trạng thái hoạt động — điều này đã được sửa. Trong phiên bản hiện tại, LiveData gửi giá trị mới nhất khi chuyển từ không hoạt động sang hoạt động, giúp đơn giản hóa việc khởi tạo màn hình.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm