Tuỳ thuộc vào vị trí mà trạng thái được chuyển lên và logic bắt buộc, bạn có thể dùng các API khác nhau để lưu trữ và khôi phục trạng thái giao diện người dùng. Mỗi ứng dụng dùng một tổ hợp API để đạt được mục tiêu này một cách hiệu quả nhất.
Mọi ứng dụng Android đều có thể mất trạng thái giao diện người dùng do việc tạo lại quá trình hoặc hoạt động. Việc mất trạng thái này có thể xảy ra do các sự kiện sau:
- Thay đổi cấu hình. Hoạt động bị huỷ bỏ và tạo lại trừ phi việc thay đổi cấu hình được xử lý theo cách thủ công.
- Sự kiện bị buộc tắt do hệ thống gây ra. Ứng dụng chạy trong nền và thiết bị giải phóng các tài nguyên (như bộ nhớ) cho các quy trình khác dùng.
Việc duy trì trạng thái sau các sự kiện này là cần thiết để mang lại trải nghiệm người dùng tích cực. Việc chọn trạng thái để duy trì tuỳ thuộc vào luồng người dùng riêng biệt của ứng dụng. Tốt nhất là bạn nên giữ nguyên trạng thái liên quan đến thao tác và hoạt động đầu vào của người dùng. Ví dụ: vị trí thanh cuộn của danh sách, mã nhận dạng của mục mà người dùng muốn biết thêm thông tin chi tiết, tiến trình lựa chọn tuỳ chọn của người dùng hoặc dữ liệu đầu vào trong các trường văn bản.
Trang này tóm tắt các API dùng để lưu trữ trạng thái giao diện người dùng, tuỳ thuộc vào vị trí trạng thái được chuyển lên và logic cần trạng thái này.
Logic giao diện người dùng
Nếu trạng thái được chuyển lên trong giao diện người dùng, trong các hàm có khả năng kết hợp hoặc các lớp phần tử giữ trạng thái thuần tuý trong phạm vi Thành phần kết hợp, thì bạn có thể dùng rememberSaveable
để giữ lại trạng thái giữa việc tạo lại quá trình và hoạt động.
Trong đoạn mã sau, rememberSaveable
được dùng để lưu trữ trạng thái boolean duy nhất của thành phần trên giao diện người dùng:
@Composable fun ChatBubble( message: Message ) { var showDetails by rememberSaveable { mutableStateOf(false) } ClickableText( text = AnnotatedString(message.content), onClick = { showDetails = !showDetails } ) if (showDetails) { Text(message.timestamp) } }
showDetails
là biến boolean dùng để lưu trữ giá trị xem bong bóng trò chuyện đang ở trạng thái thu gọn hoặc mở rộng.
rememberSaveable
lưu trữ trạng thái của thành phần trên giao diện người dùng trong Bundle
thông qua cơ chế trạng thái của thực thể đã lưu.
Hàm này có thể tự động lưu trữ các kiểu dữ liệu nguyên thuỷ vào gói. Nếu trạng thái của bạn được giữ trong một kiểu không phải là kiểu dữ liệu nguyên thuỷ, ví dụ như lớp dữ liệu, bạn có thể dùng các cơ chế lưu trữ khác, chẳng hạn như dùng chú giải Parcelize
, dùng API Compose như listSaver
và mapSaver
hoặc triển khai một lớp trình lưu trữ tuỳ chỉnh mở rộng lớp Saver
thời gian chạy trong Compose. Xem tài liệu về Các cách lưu trữ trạng thái để tìm hiểu thêm về các phương thức này.
Trong đoạn mã sau, API Compose rememberLazyListState
dùng rememberSaveable
để lưu trữ LazyListState
, trong đó có chứa trạng thái cuộn của LazyColumn
hoặc LazyRow
. Phương thức này sử dụng LazyListState.Saver
. Đây là một trình lưu trữ tuỳ chỉnh có thể lưu trữ và khôi phục trạng thái cuộn. Sau khi tạo lại một hoạt động hoặc quy trình (ví dụ: sau khi có sự thay đổi cấu hình như thay đổi hướng của thiết bị), trạng thái cuộn sẽ được giữ nguyên.
@Composable fun rememberLazyListState( initialFirstVisibleItemIndex: Int = 0, initialFirstVisibleItemScrollOffset: Int = 0 ): LazyListState { return rememberSaveable(saver = LazyListState.Saver) { LazyListState( initialFirstVisibleItemIndex, initialFirstVisibleItemScrollOffset ) } }
Phương pháp hay nhất
rememberSaveable
sử dụng Bundle
để lưu trữ trạng thái của giao diện người dùng (trạng thái này được chia sẻ bởi các API khác, vốn cũng có thể ghi vào trạng thái này), ví dụ như các lệnh gọi onSaveInstanceState()
trong hoạt động của bạn. Tuy nhiên, kích thước của Bundle
này bị hạn chế và việc lưu trữ các đối tượng lớn có thể dẫn đến các ngoại lệ TransactionTooLarge
trong thời gian chạy. Điều này có thể đặc biệt rắc rối trong các ứng dụng dùng kiến trúc hoạt động đơn (single Activity
) vì các ứng dụng này dùng cùng một Bundle
trong ứng dụng.
Để tránh loại sự cố này, bạn không nên lưu trữ các đối tượng vừa lớn lại vừa phức tạp hoặc không nên lưu trữ danh sách đối tượng trong gói.
Thay vào đó, hãy lưu trữ trạng thái tối thiểu cần thiết, chẳng hạn như mã nhận dạng hoặc khoá, đồng thời sử dụng các trạng thái này để uỷ quyền khôi phục thêm trạng thái giao diện người dùng phức tạp cho các cơ chế khác, chẳng hạn như bộ nhớ liên tục.
Các lựa chọn thiết kế này phụ thuộc vào các trường hợp sử dụng cụ thể của ứng dụng và hành vi mà người dùng mong đợi.
Xác minh quá trình khôi phục trạng thái
Bạn có thể xác minh rằng trạng thái được lưu trữ bằng rememberSaveable
ở các thành phần trong Compose được khôi phục đúng cách khi hoạt động hoặc quy trình được tạo lại. Có một số API cho mục đích này, chẳng hạn như StateRestorationTester
. Hãy xem tài liệu về Kiểm thử để tìm hiểu thêm.
Logic nghiệp vụ
Nếu trạng thái của thành phần trên giao diện người dùng được chuyển lên ViewModel
vì đây là trạng thái được logic kinh doanh yêu cầu, thì bạn có thể dùng các API của ViewModel
.
Một trong những lợi ích chính của việc dùng ViewModel
trong ứng dụng Android là giúp xử lý miễn phí các thay đổi về cấu hình. Khi có thay đổi về cấu hình cũng như khi hoạt động bị huỷ bỏ và được tạo lại, trạng thái giao diện người dùng được chuyển lên ViewModel
sẽ được lưu trong bộ nhớ. Sau khi tạo lại, thực thể ViewModel
cũ sẽ được đính kèm vào thực thể hoạt động mới.
Tuy nhiên, thực thể ViewModel
không vượt qua được sự kiện bị buộc tắt do hệ thống gây ra.
Để trạng thái giao diện người dùng vượt qua được sự kiện này, hãy sử dụng mô-đun Trạng thái đã lưu cho ViewModel, trong đó có chứa API SavedStateHandle
.
Phương pháp hay nhất
SavedStateHandle
cũng sử dụng cơ chế Bundle
để lưu trữ trạng thái giao diện người dùng. Vì vậy, bạn chỉ nên sử dụng cơ chế này để lưu trữ trạng thái của thành phần trên giao diện người dùng đơn giản.
Bạn không nên lưu trữ trạng thái giao diện người dùng trên màn hình (được tạo bằng cách áp dụng các quy tắc nghiệp vụ và truy cập các lớp của ứng dụng ngoài giao diện người dùng) vào SavedStateHandle
do độ phức tạp và kích thước của ứng dụng. Bạn có thể dùng các cơ chế khác nhau để lưu trữ dữ liệu phức tạp hoặc dữ liệu có kích thước lớn, như bộ nhớ cục bộ liên tục. Sau khi tạo lại quy trình, màn hình sẽ được tạo lại bằng trạng thái tạm thời đã khôi phục được lưu trữ trong SavedStateHandle
(nếu có) và trạng thái giao diện người dùng trên màn hình được tạo lại từ lớp dữ liệu.
Các API SavedStateHandle
SavedStateHandle
có nhiều API để lưu trữ trạng thái của thành phần trên giao diện người dùng, đáng chú ý nhất là:
Compose State |
saveable() |
---|---|
StateFlow |
getStateFlow() |
Compose State
Sử dụng API saveable
của SavedStateHandle
để đọc và ghi trạng thái của thành phần trên giao diện người dùng dưới dạng MutableState
, để đảm bảo API này vượt qua được quá trình tạo lại quá trình và hoạt động với quá trình thiết lập mã ở mức tối thiểu.
API saveable
hỗ trợ các loại chính ngay từ đầu và nhận tham số stateSaver
để sử dụng trình lưu tuỳ chỉnh, chẳng hạn như rememberSaveable()
.
Trong đoạn mã sau, message
lưu trữ các loại dữ liệu từ hoạt động đầu vào của người dùng vào TextField
:
class ConversationViewModel( savedStateHandle: SavedStateHandle ) : ViewModel() { var message by savedStateHandle.saveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue("")) } private set fun update(newMessage: TextFieldValue) { message = newMessage } /*...*/ } val viewModel = ConversationViewModel(SavedStateHandle()) @Composable fun UserInput(/*...*/) { TextField( value = viewModel.message, onValueChange = { viewModel.update(it) } ) }
Xem tài liệu SavedStateHandle
để biết thêm thông tin về cách sử dụng API saveable
.
StateFlow
Sử dụng getStateFlow()
để lưu trữ trạng thái của thành phần trên giao diện người dùng và sử dụng trạng thái này dưới dạng luồng từ SavedStateHandle
. StateFlow
ở chế độ chỉ có thể đọc và API yêu cầu bạn chỉ định khoá để có thể thay thế flow (luồng) giúp tạo giá trị mới. Với khoá đã định cấu hình, bạn có thể truy xuất StateFlow
và thu thập giá trị mới nhất.
Trong đoạn mã sau, savedFilterType
là biến StateFlow
giúp lưu trữ loại bộ lọc áp dụng cho danh sách kênh trò chuyện trong ứng dụng trò chuyện:
private const val CHANNEL_FILTER_SAVED_STATE_KEY = "ChannelFilterKey" class ChannelViewModel( channelsRepository: ChannelsRepository, private val savedStateHandle: SavedStateHandle ) : ViewModel() { private val savedFilterType: StateFlow<ChannelsFilterType> = savedStateHandle.getStateFlow( key = CHANNEL_FILTER_SAVED_STATE_KEY, initialValue = ChannelsFilterType.ALL_CHANNELS ) private val filteredChannels: Flow<List<Channel>> = combine(channelsRepository.getAll(), savedFilterType) { channels, type -> filter(channels, type) }.onStart { emit(emptyList()) } fun setFiltering(requestType: ChannelsFilterType) { savedStateHandle[CHANNEL_FILTER_SAVED_STATE_KEY] = requestType } /*...*/ } enum class ChannelsFilterType { ALL_CHANNELS, RECENT_CHANNELS, ARCHIVED_CHANNELS }
Mỗi khi người dùng chọn một loại bộ lọc mới, setFiltering
sẽ được gọi. Thao tác này sẽ lưu một giá trị mới trong SavedStateHandle
được lưu trữ bằng khoá _CHANNEL_FILTER_SAVED_STATE_KEY_
. savedFilterType
là một flow (luồng) tạo giá trị mới nhất được lưu trữ vào khoá. filteredChannels
được đăng ký với flow (luồng) để thực hiện việc lọc kênh.
Xem tài liệu SavedStateHandle
để biết thêm thông tin về API getStateFlow()
.
Tóm tắt
Bảng sau đây tóm tắt các API được đề cập trong phần này và thời điểm sử dụng từng API để lưu trạng thái của giao diện người dùng:
Sự kiện | Logic giao diện người dùng | Logic nghiệp vụ trong ViewModel |
---|---|---|
Các thay đổi về cấu hình | rememberSaveable |
Tự động |
Sự kiện bị buộc tắt do hệ thống gây ra | rememberSaveable |
SavedStateHandle |
API cần sử dụng phụ thuộc vào vị trí lưu giữ trạng thái và logic mà API đó yêu cầu. Đối với trạng thái dùng trong logic giao diện người dùng, hãy sử dụng rememberSaveable
. Đối với trạng thái dùng trong logic nghiệp vụ, nếu bạn giữ trạng thái đó trong ViewModel
, hãy lưu bằng cách dùng SavedStateHandle
.
Bạn nên dùng các API gói (rememberSaveable
và SavedStateHandle
) để lưu trữ một lượng nhỏ trạng thái giao diện người dùng. Đây là dữ liệu tối thiểu cần thiết để khôi phục giao diện người dùng về trạng thái trước đó, cùng với các cơ chế lưu trữ khác. Ví dụ: nếu lưu trữ mã nhận dạng của một hồ sơ mà người dùng đang xem trong gói, bạn có thể tìm nạp nhiều dữ liệu, chẳng hạn như thông tin chi tiết về hồ sơ, từ lớp dữ liệu.
Để biết thêm thông tin về các cách lưu trạng thái giao diện người dùng, hãy xem tài liệu chung về cách lưu trạng thái giao diện người dùng và trang lớp dữ liệu của hướng dẫn cấu trúc.
Đề xuất cho bạn
- Lưu ý: văn bản có đường liên kết sẽ hiện khi JavaScript tắt
- Vị trí để chuyển trạng thái lên trên
- Trạng thái và Jetpack Compose
- Danh sách và lưới