Saved State module for ViewModel Part of Android Jetpack.
همانطور که در ذخیره کردن حالتهای میانای کاربر ذکر شد، اشیای ViewModel میتوانند تغییرات پیکربندی را مدیریت کنند، بنابراین لازم نیست نگران حالت در چرخشها یا موارد دیگر باشید. بااینحال، اگر لازم است مرگ پردازش آغازشده توسط سیستم را مدیریت کنید،
شاید بخواهید از SavedStateHandle API بهعنوان پشتیبان استفاده کنید.
وضعیت میانای کاربر معمولاً در اشیاء ViewModel ذخیره یا ارجاع داده میشود، بنابراین استفاده از
rememberSaveable در Compose به مقداری کد تکراری نیاز دارد که
واحد وضعیت ذخیرهشده میتواند آن را برای شما مدیریت کند.
هنگام استفاده از این ماژول، اشیاء ViewModel یک شیء SavedStateHandle را ازطریق سازنده آن دریافت میکنند. این شیء یک نقشه کلید-مقدار است که به شما امکان میدهد
اشیاء را در وضعیت ذخیرهشده بنویسید و بازیابی کنید. این مقادیر
پساز توقف فرایند توسط سیستم باقی میمانند و ازطریق همان شیء
دردسترس خواهند بود.
وضعیت ذخیرهشده به پشته تکلیف شما گره خورده است. اگر پشته تکلیف شما ازبین برود، وضعیت ذخیرهشده شما نیز ازبین میرود. این اتفاق ممکن است هنگام توقف اجباری برنامه، برداشتن برنامه از منو موارد اخیر، یا بازراهاندازی دستگاه رخ دهد. دراینصورت، پشته تکلیف ناپدید میشود و نمیتوانید اطلاعات را در حالت ذخیرهشده بازیابی کنید. در سناریوهای رد کردن وضعیت میانای کاربر، وضعیت ذخیرهشده بازیابی نمیشود. در سناریوهای راهاندازیشده توسط سیستم، بله.
راهاندازی
برای استفاده از SavedStateHandle، آن را بهعنوان آرگومان سازنده برای
ViewModel خود بپذیرید.
class SavedStateViewModel(private val state: SavedStateHandle) : ViewModel() { ... }
سپس میتوانید نمونهای از ViewModel را در composable خود
بدون هیچ پیکربندی اضافی بازیابی کنید. کارخانه پیشفرض ViewModel،
SavedStateHandle مناسب را برای ViewModel شما ارائه میدهد.
class MyViewModel : ViewModel() { /*...*/ } // import androidx.lifecycle.viewmodel.compose.viewModel @Composable fun MyScreen( viewModel: MyViewModel = viewModel() ) { // use viewModel here }
هنگام ارائه نمونه ViewModelProvider.Factory سفارشی، میتوانید
استفاده از SavedStateHandle را بااستفاده از CreationExtras و
viewModelFactory DSL فعال کنید.
کار کردن با SavedStateHandle
کلاس SavedStateHandle یک نقشه کلید-مقدار است که به شما امکان میدهد دادهها را ازطریق روشهای set() و get() در وضعیت ذخیرهشده بنویسید و بازیابی کنید.
بااستفاده از SavedStateHandle، مقدار پُرسمان درطول مرگ پردازش حفظ میشود
و مطمئن میشوید که کاربر همان مجموعه دادههای فیلترشده را قبل و بعداز
بازآفرینی میبیند، بدون اینکه فعالیت یا قطعه نیاز به ذخیره، بازیابی،
و ارسال دستی آن مقدار به ViewModel داشته باشد.
SavedStateHandle همچنین روشهای دیگری دارد که ممکن است هنگام تعامل با نقشه مقدار کلید انتظار داشته باشید:
contains(String key)- بررسی میکند که آیا مقداری برای کلید دادهشده وجود دارد یا نه.remove(String key)- مقدار کلید دادهشده را برمیدارد.keys()- همه کلیدهای موجود درSavedStateHandleرا برمیگرداند.
علاوهبراین، میتوانید مقادیر را از SavedStateHandle بااستفاده از
نگهدارنده دادههای دیدهشدنی بازیابی کنید. فهرست انواع پشتیبانیشده شامل موارد زیر است:
StateFlow
میتوانید مقادیر را از SavedStateHandle که در یک
مشاهدهپذیر StateFlow پیچیده شده است بازیابی کنید. بسته به اینکه آیا نیاز به تغییر مستقیم مقدار دارید یا نه، میتوانید
بین یک جریان فقط خواندنی یا قابل تغییر انتخاب کنید:
getStateFlow(): اگر فقط نیاز به خواندن وضعیت دارید، از این مورد استفاده کنید. وقتی مقدار کلید را در جای دیگری درSavedStateHandleبهروزرسانی میکنید، StateFlow مقدار جدید را دریافت میکند. این حالت زمانی ایدهآل است که بخواهید یک جاریسازی فقط خواندنی را نمایش دهید و آن را بااستفاده از عملگرهای Flow تبدیل کنید.-
getMutableStateFlow(): اگر به دسترسی خواندن و نوشتن نیاز دارید، از این گزینه استفاده کنید. بهروزرسانی.valueMutableStateFlowبرگرداندهشده بهطور خودکارSavedStateHandleزیربنایی را بهروز میکند و شما را از نیاز به تنظیم دستی کلید بینیاز میکند.
اغلب، این مقادیر را بهدلیل تعاملات کاربر بهروز میکنید، مانند وارد کردن یک پُرسمان برای فیلتر کردن فهرستی از دادهها.
class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() { // Use getMutableStateFlow to read and write the query directly private val _query = savedStateHandle.getMutableStateFlow("query", "") val query: StateFlow= _query.asStateFlow() // Use getStateFlow if you only need a read-only stream to react to changes val filteredData: StateFlow<List > = query.flatMapLatest { repository.getFilteredData(it) } .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000), initialValue = emptyList() ) fun setQuery(newQuery: String) { // Updating the MutableStateFlow automatically updates the SavedStateHandle _query.value = newQuery } }
پشتیبانی از سریالسازی KotlinX
برای وضعیت پیچیده واسط کاربر، میتوانید از نماینده ویژگی saved در کنار
KotlinX Serialization استفاده کنید. این نماینده به شما امکان میدهد کلاسهای داده @Serializable سفارشی را مستقیماً در SavedStateHandle حفظ کنید. این کار وضعیت ViewModel شما را درطول مرگ پردازش حفظ میکند، بنابراین Compose UI شما میتواند بهطور یکپارچه وضعیت خود را پساز بازآفرینی بازیابی کند.
برای استفاده از آن، کلاس دادهتان را با @Serializable حاشیهنویسی کنید و از نماینده saved در ViewModel خود استفاده کنید:
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel // Ensure you have the savedstate-ktx dependency import androidx.savedstate.serialization.saved import kotlinx.serialization.Serializable @Serializable data class UserFilterState( val searchQuery: String, val minAge: Int, val includeInactive: Boolean ) class FilterViewModel(savedStateHandle: SavedStateHandle) : ViewModel() { // The state is automatically serialized to a Bundle on process death, // and deserialized upon recreation. var filterState by savedStateHandle.saved { UserFilterState(searchQuery = "", minAge = 18, includeInactive = false) } fun updateQuery(newQuery: String) { // Mutating the property automatically updates the underlying SavedStateHandle filterState = filterState.copy(searchQuery = newQuery) } }
پشتیبانی از Compose State
اگر وضعیت شما بهجای KotlinX Serialization به میاناهای برنامهسازی کاربردی Saver Compose متکی است، آرتیفکت lifecycle-viewmodel-compose نماینده saveable را ارائه میدهد. این امر امکان همکنشپذیری بین
SavedStateHandle و Saver در Compose را فراهم میکند تا هر State که میتوانید
ازطریق rememberSaveable با Saver سفارشی ذخیره کنید، بتواند با
SavedStateHandle نیز ذخیره شود.
class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() { var filteredData: List<String> by savedStateHandle.saveable { mutableStateOf(emptyList()) } fun setQuery(query: String) { withMutableSnapshot { filteredData += query } } }
نوعهای پشتیبانیشده
دادههای نگهداریشده در SavedStateHandle بهعنوان Bundle ذخیره و بازیابی میشود،
همراه با بقیه savedInstanceState برای برنامهتان.
انواع مستقیماً پشتیبانیشده
بهطور پیشفرض، میتوانید set() و get() را در SavedStateHandle برای همان انواع داده Bundle فراخوانی کنید، همانطور که در زیر نشان داده شده است:
| پشتیبانی از نوع/کلاس | پشتیبانی از آرایه |
double |
double[] |
int |
int[] |
long |
long[] |
String |
String[] |
byte |
byte[] |
char |
char[] |
CharSequence |
CharSequence[] |
float |
float[] |
Parcelable |
Parcelable[] |
Serializable |
Serializable[] |
short |
short[] |
SparseArray |
|
Binder |
|
Bundle |
|
ArrayList |
|
Size (only in API 21+) |
|
SizeF (only in API 21+) |
اگر کلاس یکی از کلاسهای فهرست بالا را گسترش نمیدهد، با افزودن حاشیهنویسی Kotlin @Parcelize یا پیادهسازی مستقیم Parcelable، کلاس را قابلبستهبندی کنید.
ذخیره کردن کلاسهای غیرقابلتجزیه
اگر کلاسی Parcelable یا Serializable را پیادهسازی نکند و نتوان آن را برای پیادهسازی یکی از این رابطها اصلاح کرد، امکان ذخیره مستقیم نمونهای از آن کلاس در SavedStateHandle وجود ندارد.
با شروع از Lifecycle 2.3.0-alpha03، SavedStateHandle به شما امکان میدهد
هر شیئی را با ارائه منطق خودتان برای ذخیره و بازیابی شیء بهعنوان
Bundle بااستفاده از روش setSavedStateProvider() ذخیره کنید.
SavedStateRegistry.SavedStateProvider میانایی است که یک روش saveState() را تعریف میکند که Bundle حاوی وضعیتی را که میخواهید ذخیره کنید برمیگرداند. وقتی SavedStateHandle آماده ذخیره کردن وضعیتش است،
saveState() را برای بازیابی Bundle از SavedStateProvider فراخوانی میکند و
Bundle را برای کلید مرتبط ذخیره میکند.
مثالی از برنامهای را درنظر بگیرید که ازطریق هدف ACTION_IMAGE_CAPTURE تصویری را از برنامه دوربین درخواست میکند و فایل موقتی را برای جایی که دوربین باید تصویر را ذخیره کند ارسال میکند. TempFileViewModel منطق ایجاد آن فایل موقت را کپسولهسازی میکند.
class TempFileViewModel : ViewModel() { private var tempFile: File? = null fun createOrGetTempFile(): File { return tempFile ?: File.createTempFile("temp", null).also { tempFile = it } } }
برای اطمینان از اینکه اگر فرایند فعالیت متوقف شد و بعداً بازیابی شد، فایل موقت ازبین نرود، TempFileViewModel میتواند از SavedStateHandle برای حفظ دادههایش استفاده کند. برای اینکه به TempFileViewModel اجازه دهید دادههایش را ذخیره کند،
SavedStateProvider را پیادهسازی کنید و آن را بهعنوان ارائهدهنده در SavedStateHandle
ViewModel تنظیم کنید:
private fun File.saveTempFile() = bundleOf("path", absolutePath) class TempFileViewModel(savedStateHandle: SavedStateHandle) : ViewModel() { private var tempFile: File? = null init { savedStateHandle.setSavedStateProvider("temp_file") { // saveState() if (tempFile != null) { tempFile.saveTempFile() } else { Bundle() } } } fun createOrGetTempFile(): File { return tempFile ?: File.createTempFile("temp", null).also { tempFile = it } } }
برای بازیابی دادههای File هنگام بازگشت کاربر، temp_file
Bundle را از SavedStateHandle بازیابی کنید. این همان Bundle ارائهشده توسط
saveTempFile() است که حاوی مسیر مطلق است. سپس میتوان از مسیر مطلق برای نمونهسازی File جدید استفاده کرد.
private fun File.saveTempFile() = bundleOf("path", absolutePath) private fun Bundle.restoreTempFile() = if (containsKey("path")) { File(getString("path")) } else { null } class TempFileViewModel(savedStateHandle: SavedStateHandle) : ViewModel() { private var tempFile: File? = null init { val tempFileBundle = savedStateHandle.get<Bundle>("temp_file") if (tempFileBundle != null) { tempFile = tempFileBundle.restoreTempFile() } savedStateHandle.setSavedStateProvider("temp_file") { // saveState() if (tempFile != null) { tempFile.saveTempFile() } else { Bundle() } } } fun createOrGetTempFile(): File { return tempFile ?: File.createTempFile("temp", null).also { tempFile = it } } }
SavedStateHandle در آزمایشها
برای آزمایش ViewModel که SavedStateHandle را بهعنوان وابستگی میگیرد،
نمونه جدیدی از SavedStateHandle با مقادیر آزمایشی موردنیاز آن ایجاد کنید و آن را
به نمونه ViewModel که درحال آزمایش آن هستید ارسال کنید.
class MyViewModelTest { private lateinit var viewModel: MyViewModel @Before fun setup() { val savedState = SavedStateHandle(mapOf("someIdArg" to testId)) viewModel = MyViewModel(savedState = savedState) } }
منابع بیشتر
برای اطلاعات بیشتر درباره ماژول «وضعیت ذخیرهشده» برای ViewModel، به منابع زیر مراجعه کنید.
Codelabs
محتوا را مشاهده میکند
توصیهشده برای شما
- توجه: وقتی جاوا اسکریپت خاموش باشد، نوشتار پیوند نمایش داده میشود
- ذخیره کردن وضعیتهای میانای کاربر
- کار کردن با اشیای دادههای دیدهشدنی
- ایجاد ViewModels با وابستگیها