State<T> và MutableState<T> của Jetpack Compose là các phần tử giữ giá trị đa năng được hệ thống ảnh chụp nhanh của Compose hỗ trợ. Mặc dù thường được dùng để thúc đẩy quá trình kết hợp lại giao diện người dùng, nhưng bản thân hệ thống ảnh chụp nhanh không liên quan đến việc kết xuất giao diện người dùng.
Với các thư viện AppState (Transform và StateStore), bạn có thể lưu giữ, lấy, quan sát và duy trì State của Compose trên các ViewModel, trình quản lý, kho lưu trữ và dịch vụ nền.
Tại sao nên sử dụng Trạng thái Compose ngoài giao diện người dùng?
Khi bạn đọc State của Compose bên trong một khối quan sát trạng thái (chẳng hạn như thành phần kết hợp giao diện người dùng, transform hoặc derivedStateOf), thời gian chạy Compose sẽ mang lại hai lợi ích chính cho mọi lớp trong cấu trúc của bạn:
- Theo dõi phần phụ thuộc tự động và linh động: Việc đọc
.valuecủa trạng thái (hoặc sử dụng tính năng uỷ quyền thuộc tínhby) sẽ tự động đăng ký trạng thái đó dưới dạng phần phụ thuộc của khối. Vì hoạt động theo dõi là động, nên các phần phụ thuộc được xác định trong thời gian chạy theo đường dẫn mã thực thi. Nếu một nhánh có điều kiện không được thực hiện (ví dụ: sau khi kiểm traifhoặcreturnsớm), thì các trạng thái bên trong nhánh đó sẽ không được đọc và các thay đổi đối với các trạng thái đó sẽ không kích hoạt các phép tính toán lại không cần thiết. - Tính nhất quán và mức độ chi tiết: Bạn có thể khai báo các thuộc tính
MutableStateriêng biệt, chi tiết và gói các hoạt động ghi nhiều thuộc tính trongSnapshot.withMutableSnapshot. Các bản cập nhật trung gian được tách biệt với ảnh chụp nhanh có thể thay đổi và được áp dụng một cách tự động khi khối hoàn tất, vì vậy, trình quan sát đọc bên trong ảnh chụp nhanh không bao giờ thấy trạng thái trung gian không nhất quán và chỉ tính toán lại khi các thuộc tính cụ thể mà họ đọc thay đổi. Không giống nhưStateFlow, bạn không phải chọn giữa mộtStateFlow<UiState>nguyên khối (làm mất hiệu lực mọi trình thu thập khi có bất kỳ trường nào thay đổi) và các thuộc tínhStateFlowriêng biệt (không thể cập nhật cùng nhau trong một giao dịch nguyên tử).
Chọn API trạng thái phù hợp trong cấu trúc của bạn
Chọn cách giữ và duy trì State của Compose dựa trên thời gian tồn tại của một phần trạng thái và những thành phần cần truy cập vào trạng thái đó:
| Danh mục tiểu bang | Vòng đời và phạm vi | Ví dụ | Các API State nên dùng trong Compose |
|---|---|---|---|
| Trạng thái hoạt động | Luôn được lưu trữ trong bộ nhớ; được truy vấn mới khi khởi động và không được duy trì khi bị buộc tắt. | Trạng thái kết nối mạng, trạng thái kết nối Bluetooth, trạng thái giao diện người dùng tạm thời, chẳng hạn như lựa chọn mục. |
|
| Trạng thái giao diện người dùng tạm thời | Vẫn tồn tại sau khi thay đổi cấu hình và bị buộc tắt do hệ thống gây ra, không được lưu trữ vô thời hạn; trong phạm vi một màn hình hoặc hoạt động. | Các phần tử trên giao diện người dùng được mở rộng hoặc thu gọn, vị trí cuộn, cụm từ tìm kiếm trên màn hình hoặc nút bật/tắt bộ lọc. |
|
| Trạng thái phiên tạm thời | Còn lại sau khi tiến trình nền bị buộc tắt, không được lưu trữ vô thời hạn; được chia sẻ giữa các hoạt động, dịch vụ hoặc tiện ích. | Bộ hẹn giờ tập trung đang hoạt động, giỏ hàng thanh toán nhiều bước hoặc bản nháp biểu mẫu đang thực hiện. |
|
| Trạng thái vĩnh viễn | Được lưu giữ vô thời hạn trên ổ đĩa. | Lựa chọn ưu tiên và chế độ cài đặt của người dùng (DataStore), dữ liệu ứng dụng có cấu trúc và thư viện nội dung nghe nhìn (Room). | Các luồng DataStore hoặc Room được thu thập bên trong transform bằng collectAsState |
Sử dụng Trạng thái Compose trong ViewModel
Bên trong ViewModel, bạn có thể khai báo các dữ liệu đầu vào màn hình độc lập dưới dạng thuộc tính MutableState chi tiết, duy trì các dữ liệu đó trong suốt quá trình buộc tắt bằng SavedStateHandle.saveable, cập nhật nhiều thuộc tính một cách riêng biệt bằng Snapshot.withMutableSnapshot và kết hợp các thuộc tính đó với các luồng Flow của kho lưu trữ bên trong transform:
sealed interface TaskListState { data object Loading : TaskListState data class Success( val tasks: List<Task>, val selectedTaskIds: Set<String>, val isOnline: Boolean = true, ) : TaskListState } @HiltViewModel class TaskListViewModel @Inject constructor( private val tasksRepository: TasksRepository, savedStateHandle: SavedStateHandle, ) : ViewModel() { var selectedTaskIds by mutableStateOf(emptySet<String>()) private set var searchQuery by savedStateHandle.saveable { mutableStateOf("") } private set fun updateSearchQuery(query: String) { Snapshot.withMutableSnapshot { searchQuery = query selectedTaskIds = emptySet() } } val tasksState: State<TaskListState> = transform<TaskListState>( initialValue = TaskListState.Loading, scope = viewModelScope, ) { val tasks = remember(searchQuery) { tasksRepository.getTasks(searchQuery) }.collectAsState(initial = null).value ?: return@transform TaskListState.Loading TaskListState.Success( tasks = tasks, selectedTaskIds = selectedTaskIds, ) } }
Các khuôn mẫu chính trong ViewModel này:
- Sử dụng
SavedStateHandle.saveablecho trạng thái đã lưu có tính phản ứng: Việc uỷ quyềnsearchQuerychoSavedStateHandle.saveablebằngmutableStateOf(từandroidx.lifecycle:lifecycle-viewmodel-compose) sẽ duy trì giá trị trong suốt quá trình bị buộc tắt dưới dạngMutableState, vì vậy,transformsẽ tự động theo dõi các lượt đọc. Tránh sử dụngSavedStateHandle.savedkhông có thuộc tính, vì hàm này trả về một thuộc tính Kotlin thuần tuý thay vìStatecủa Compose và không kích hoạt quá trình kết hợp lại khi thay đổi. - Ghi nhiều thuộc tính nguyên tử bằng
Snapshot.withMutableSnapshot: Việc cập nhậtsearchQueryvàselectedTaskIdsbên trongSnapshot.withMutableSnapshotsẽ tách biệt các lượt ghi và cam kết cả hai thay đổi trong một giao dịch tổng quan nhanh nguyên tử, vì vậy, trình quan sát (bao gồm cảtransform) sẽ thấy cả hai bản cập nhật cùng nhau trong một lượt kết hợp lại thay vì quan sátsearchQuerymới được ghép nối vớiselectedTaskIdscũ. - Sử dụng lại các luồng trong quá trình kết hợp lại bằng
remember(key): Việc góitasksRepository.getTasks(searchQuery)trongrememberbằngsearchQuerylàm khoá đảm bảo rằng khiselectedTaskIdsthay đổi và kết hợp lại khốitransform, Compose sẽ sử dụng lạiFlowcơ sở dữ liệu hiện có thay vì khởi động lại truy vấn.
Chia sẻ trạng thái hệ thống trực tiếp trên ứng dụng
Đối với trạng thái hệ thống trực tiếp được chia sẻ trên nhiều ViewModel, hoạt động hoặc dịch vụ (chẳng hạn như trạng thái kết nối mạng hoặc Bluetooth), bạn không nên duy trì trạng thái cũ vào ổ đĩa khi quá trình bị buộc tắt. Thay vào đó, hãy truy vấn trạng thái hệ thống hiện tại khi khởi động, giữ trạng thái đó trong thuộc tính mutableStateOf riêng tư bên trong một singleton trong phạm vi ứng dụng và hiển thị State<T> chỉ có thể đọc:
interface NetworkMonitor { val isOnline: State<Boolean> } @Singleton class LiveNetworkMonitor @Inject constructor( @ApplicationContext context: Context, ) : NetworkMonitor { private val connectivityManager = context.getSystemService(ConnectivityManager::class.java) private val _isOnline = mutableStateOf(checkCurrentConnectivity()) override val isOnline: State<Boolean> = _isOnline private val callback = object : ConnectivityManager.NetworkCallback() { override fun onCapabilitiesChanged( network: Network, networkCapabilities: NetworkCapabilities, ) { _isOnline.value = networkCapabilities.hasCapability( NetworkCapabilities.NET_CAPABILITY_INTERNET ) } override fun onLost(network: Network) { _isOnline.value = false } } init { connectivityManager.registerDefaultNetworkCallback(callback) } private fun checkCurrentConnectivity(): Boolean { val network = connectivityManager.activeNetwork ?: return false val capabilities = connectivityManager.getNetworkCapabilities(network) ?: return false return capabilities.hasCapability( NetworkCapabilities.NET_CAPABILITY_INTERNET ) } }
MutableState hỗ trợ việc đọc và cập nhật nhất quán từ bất kỳ luồng nào, vì vậy, NetworkCallback có thể cập nhật _isOnline.value trực tiếp từ luồng gọi lại trong nền. Nếu lệnh gọi lại trong nền cập nhật nhiều thuộc tính MutableState cùng nhau, hãy gói các lượt ghi trong Snapshot.withMutableSnapshot để các thuộc tính đó được áp dụng một cách riêng biệt.
Mọi thành phần trong ứng dụng đều có thể đọc networkMonitor.isOnline.value, cho dù là dưới dạng một lần kiểm tra, bên trong một composable giao diện người dùng hay bên trong một khối quan sát state như listener, derivedStateOf hoặc transform. Ví dụ: TaskListViewModel có thể đọc networkMonitor.isOnline.value bên trong khối transform:
val tasksState: State<TaskListState> = transform<TaskListState>( initialValue = TaskListState.Loading, scope = viewModelScope, ) { val tasks = remember(searchQuery) { tasksRepository.getTasks(searchQuery) }.collectAsState(initial = null).value ?: return@transform TaskListState.Loading TaskListState.Success( tasks = tasks, selectedTaskIds = selectedTaskIds, isOnline = networkMonitor.isOnline.value, ) }
Vì transform theo dõi networkMonitor.isOnline.value cùng với searchQuery, selectedTaskIds và kho lưu trữ Flow, nên mọi thay đổi đối với khả năng kết nối sẽ tự động tính toán lại TaskListState.Success mà không cần truy vấn lại cơ sở dữ liệu.