Màn hình tìm kiếm vẫn giữ từ khóa sau khi xoay máy, nhưng quay lại ứng dụng sau một lúc lại mất hết. Nếu dữ liệu chỉ nằm trong ViewModel, kết quả đó có thể phù hợp với vòng đời Android: thay đổi cấu hình và việc process bị hủy là hai tình huống khác nhau.
Bài này giúp bạn chọn phần trạng thái nên lưu và xây một ViewModel nhỏ bằng SavedStateHandle. Tài liệu được đọc ngày 24/10/2026. API getStateFlow được AndroidX ghi nhận từ phiên bản 2.5.0; đây là mốc API, không phải khuyến nghị ghim dependency vào bản cũ. Mã Kotlin đã được rà theo chữ ký tài liệu, chưa build hoặc thử phục hồi trên thiết bị trong lượt biên tập.
1. Ba nơi lưu, ba mục đích
Hướng dẫn lưu UI state phân biệt dữ liệu trong ViewModel, saved state và lưu trữ local. ViewModel có thể sống qua việc Activity được tạo lại do thay đổi cấu hình; bản thân đối tượng trong bộ nhớ không sống qua process death.
SavedStateHandle cung cấp nơi giữ phần trạng thái nhỏ cần phục hồi khi hệ thống tạo lại process trong ngữ cảnh được hỗ trợ. Nó gắn với saved state của host và task, không phải database. Hướng dẫn saved state cho ViewModel cũng nêu giới hạn khi task bị loại bỏ, force-stop hoặc khởi động lại thiết bị.
Với màn tìm kiếm, có thể giữ query, ID bộ lọc và mục đang chọn. Danh sách kết quả lớn, ảnh hoặc dữ liệu nghiệp vụ cần bền vững thuộc trách nhiệm repository và lưu trữ phù hợp. Khi phục hồi, dùng query hoặc ID để tải lại nội dung; đừng biến saved state thành bản sao toàn bộ màn hình.
2. Một ViewModel chỉ giữ từ khóa
Đặt lớp sau vào module đã dùng AndroidX Lifecycle và Kotlin coroutines. Giữ phiên bản các dependency Lifecycle nhất quán theo cấu hình dự án; không thêm một bản mới tùy ý chỉ để đoạn mã hết lỗi.
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.StateFlow
class SearchViewModel(
private val state: SavedStateHandle
) : ViewModel() {
val query: StateFlow<String> = state.getStateFlow(KEY_QUERY, "")
fun updateQuery(value: String) {
state[KEY_QUERY] = value
}
companion object {
private const val KEY_QUERY = "search.query"
}
}
Theo tham chiếu SavedStateHandle, getStateFlow trả luồng đọc và nhận giá trị ban đầu nếu chưa có giá trị cho key. Khi key đã được phục hồi, chuỗi rỗng không ghi đè dữ liệu đó. Việc gán vào handle cập nhật nguồn trạng thái của luồng; giao diện nhận luồng qua cơ chế thu thập phù hợp vòng đời mà dự án đang dùng.
Ví dụ dùng String, một giá trị nhỏ và phù hợp saved state. Gọi cập nhật từ luồng UI theo yêu cầu main thread của API. Trong ứng dụng thật, quy tắc kiểm tra query và việc gọi repository nên được thiết kế riêng; đoạn lớp này không tự khởi chạy tìm kiếm hay chống request cũ.
3. Để framework cấp đúng handle
Không tạo SearchViewModel(SavedStateHandle()) trực tiếp trong code màn hình rồi kỳ vọng framework tự phục hồi nó. Handle tự dựng không tự gắn với saved-state owner của Activity hoặc navigation entry.
Các factory mặc định phù hợp của AndroidX có thể cung cấp handle. Nếu dự án dùng factory tùy chỉnh hoặc dependency injection, kiểm tra cách factory nhận CreationExtras và tạo handle gắn với owner. Hãy sửa đúng đường khởi tạo của dự án, thay vì copy thêm một factory thứ hai bên ngoài hệ thống đang dùng.
Khi có nhiều màn tìm kiếm, scope ViewModel cũng quan trọng. Query của màn A không nên vô tình trở thành query màn B chỉ vì cả hai lấy cùng một instance ở Activity. Chọn owner theo vòng đời của trải nghiệm người dùng, rồi mới kiểm tra cơ chế lưu.
4. Xác định kỳ vọng phục hồi
Khi người dùng xoay màn hình, kỳ vọng là query và nội dung đang xem tiếp tục hợp lý. Khi ứng dụng vào nền rồi process bị hệ thống hủy, saved state có thể giúp dựng lại query và bắt đầu tải lại dữ liệu. Khi người dùng chủ động đóng task, force-stop hoặc khởi động lại máy, không dùng SavedStateHandle để cam kết giữ trạng thái mãi.
Tài liệu còn lưu ý thời điểm host đi qua onStop: các ghi trong lúc host đã dừng có thể chưa được lưu cho đến khi host đi qua chu kỳ start rồi stop tiếp theo. Nếu một thay đổi phải bền vững ngay theo yêu cầu sản phẩm, đưa nó vào tầng lưu trữ phù hợp thay vì dựa vào lần snapshot UI state.
Kiểm tra trên dự án của bạn
Tạo ba ca riêng: xoay máy khi đang nhập query; đưa app vào nền rồi thực hiện kịch bản process recreation có kiểm soát trên thiết bị thử; chủ động loại task rồi mở lại. Ghi rõ thao tác, hệ điều hành và kết quả mong đợi cho từng ca. Không xem một lần xoay máy thành bằng chứng đã qua process death.
Tùy chọn developer “Don't keep activities” chủ yếu giúp lộ vấn đề vòng đời Activity; nó không đồng nghĩa mọi tình huống process death. Dùng quy trình kiểm thử riêng của dự án cho việc tái tạo process và xác minh rằng query được khôi phục trước khi repository tải lại kết quả.
Nếu từ khóa luôn rỗng, rà owner, factory và key. Nếu query cũ xuất hiện ở màn khác, rà scope. Nếu phục hồi query nhưng danh sách không tải lại, kiểm tra luồng sử dụng query; saved state chỉ giữ dữ liệu, không tự thực thi nghiệp vụ.
Kết luận
Giữ những mảnh UI state nhỏ trong saved state và dùng chúng để dựng lại màn hình. Dữ liệu cần tồn tại lâu dài vẫn cần tầng lưu trữ có chủ đích. Phân biệt ba cơ chế này giúp bạn viết kỳ vọng kiểm thử đúng trước khi vá lỗi “mất state”.




