وحدة الحالة المحفوظة لـ ViewModel جزء من Android Jetpack.
كما ذكرنا في المقالة
حفظ حالات واجهة المستخدم،
يمكن لكائنات ViewModel
التعامل مع
التغييرات في الإعدادات، كي لا تقلق بشأن الحالة في عمليات التدوير أو غيرها من الحالات. مع ذلك، إذا كنت بحاجة إلى التعامل مع انتهاء العملية التي بدأها النظام،
قد تحتاج إلى استخدام واجهة برمجة التطبيقات SavedStateHandle
API كنسخة احتياطية.
عادةً ما يتم تخزين حالة واجهة المستخدم أو الإشارة إليها في كائنات ViewModel
وليس
في أنشطة، لذا فإن استخدام onSaveInstanceState()
أو rememberSaveable
يتطلب
بعض النماذج النموذجية التي يمكن أن تعالجها وحدة الحالة المحفوظة.
عند استخدام هذه الوحدة، يتلقى كائنات ViewModel
كائن SavedStateHandle
من خلال الدالة الإنشائية له. هذا الكائن عبارة عن خريطة قيمة أساسية تتيح لك
كتابة الكائنات واستردادها من الحالة المحفوظة وإليها. وتظل هذه القيم متاحة بعد أن يقاتل النظام العملية وتظل متاحة من خلال العنصر نفسه.
ترتبط الحالة المحفوظة بحزمة المهام. إذا اختفت حزمة المهام الخاصة بك، فإن الحالة المحفوظة تختفي أيضًا. يمكن أن يحدث هذا عند فرض إيقاف أحد التطبيقات، أو إزالته من قائمة التطبيقات المستخدمة مؤخرًا، أو إعادة تشغيل الجهاز. وفي هذه الحالات، يختفي تكديس المهام ولن تتمكن من استعادة المعلومات في الحالة المحفوظة. في سيناريوهات إغلاق حالة واجهة المستخدم التي يبدأها المستخدم، لا تتم استعادة الحالة المحفوظة. في سيناريوهات يبدأها النظام، يكون الأمر كذلك.
ضبط إعدادات
بدءًا من Fragment 1.2.0
أو تبعيته المتبادلة
النشاط 1.1.0، يمكنك قبول
SavedStateHandle
كوسيطة إنشائية لـ ViewModel
.
Kotlin
class SavedStateViewModel(private val state: SavedStateHandle) : ViewModel() { ... }
Java
public class SavedStateViewModel extends ViewModel { private SavedStateHandle state; public SavedStateViewModel(SavedStateHandle savedStateHandle) { state = savedStateHandle; } ... }
يمكنك بعد ذلك استرداد مثيل ViewModel
بدون أي إعدادات
إضافية. يوفّر المصنع التلقائي لجهاز ViewModel
SavedStateHandle
المناسب لجهاز ViewModel
.
Kotlin
class MainFragment : Fragment() { val vm: SavedStateViewModel by viewModels() ... }
Java
class MainFragment extends Fragment { private SavedStateViewModel vm; public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { vm = new ViewModelProvider(this).get(SavedStateViewModel.class); ... } ... }
عند توفير مثيل
ViewModelProvider.Factory
مخصّص، يمكنك تفعيل استخدام SavedStateHandle
من خلال توسيع نطاق
AbstractSavedStateViewModelFactory
.
العمل باستخدام SaveStateHandle
الفئة SavedStateHandle
هي خريطة قيمة أساسية تتيح لك كتابة البيانات واستردادها من الحالة المحفوظة وإليها من خلال طريقتَي
set()
وget()
.
باستخدام SavedStateHandle
، يتم الاحتفاظ بقيمة طلب البحث عند انتهاء عملية إيقاف العملية،
لضمان ظهور مجموعة البيانات المفلتَرة نفسها للمستخدم قبل عملية الإنشاء وبعدها بدون إجراء أي نشاط أو جزء يحتاج إلى حفظ تلك القيمة واستعادتها
وإعادة توجيهها يدويًا إلى ViewModel
.
لدى SavedStateHandle
أيضًا طرق أخرى قد تتوقعها عند التفاعل مع خريطة قيمة أساسية:
contains(String key)
- للتحقّق من وجود قيمة للمفتاح المحدّد.remove(String key)
- لإزالة قيمة المفتاح المحدَّد.keys()
- لعرض جميع المفاتيح المضمّنة فيSavedStateHandle
.
بالإضافة إلى ذلك، يمكنك استرداد القيم من SavedStateHandle
باستخدام
صاحب بيانات يمكن ملاحظته. في ما يلي قائمة بالأنواع المتاحة:
بيانات مباشرة
يمكنك استرداد القيم من SavedStateHandle
التي تم تضمينها في
LiveData
يمكن ملاحظتها باستخدام
getLiveData()
.
عندما يتم تعديل قيمة المفتاح، ستتلقّى LiveData
القيمة الجديدة. في أغلب الأحيان، يتم ضبط القيمة بسبب تفاعلات المستخدم، مثل إدخال طلب بحث لفلترة قائمة البيانات. يمكن بعد ذلك استخدام هذه القيمة المعدّلة
لتحويل LiveData
.
Kotlin
class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() { val filteredData: LiveData<List<String>> = savedStateHandle.getLiveData<String>("query").switchMap { query -> repository.getFilteredData(query) } fun setQuery(query: String) { savedStateHandle["query"] = query } }
Java
public class SavedStateViewModel extends ViewModel { private SavedStateHandle savedStateHandle; public LiveData<List<String>> filteredData; public SavedStateViewModel(SavedStateHandle savedStateHandle) { this.savedStateHandle = savedStateHandle; LiveData<String> queryLiveData = savedStateHandle.getLiveData("query"); filteredData = Transformations.switchMap(queryLiveData, query -> { return repository.getFilteredData(query); }); } public void setQuery(String query) { savedStateHandle.set("query", query); } }
StateFlow
.يمكنك استرداد القيم من SavedStateHandle
التي تم تضمينها في
StateFlow
يمكن ملاحظتها باستخدام
getStateFlow()
.
عند تعديل قيمة المفتاح، ستتلقّى StateFlow
القيمة الجديدة. في معظم الأحيان، يمكنك ضبط القيمة بسبب تفاعلات المستخدم، مثل إدخال
استعلام لتصفية قائمة البيانات. يمكنك بعد ذلك تحويل هذه القيمة المحدّثة باستخدام
عوامل التدفق الأخرى.
Kotlin
class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() { val filteredData: StateFlow<List<String>> = savedStateHandle.getStateFlow<String>("query") .flatMapLatest { query -> repository.getFilteredData(query) } fun setQuery(query: String) { savedStateHandle["query"] = query } }
الدعم التجريبي لحالة Compose
يوفّر العنصر lifecycle-viewmodel-compose
واجهات برمجة تطبيقات
saveable
التجريبية التي تسمح بإمكانية التشغيل التفاعلي بين SavedStateHandle
وCompose
Saver
بحيث يمكن أيضًا حفظ أي State
يمكنك حفظه من خلال rememberSaveable
باستخدام Saver
مخصّص باستخدام SavedStateHandle
.
Kotlin
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+) |
إذا كان الصف لا يكمّل أحد العناصر المذكورة في القائمة أعلاه، يمكنك جعل
الفئة سهلة من خلال إضافة تعليق @Parcelize
التوضيحي من Kotlin أو تنفيذ
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
منطق إنشاء هذا الملف المؤقت.
Kotlin
class TempFileViewModel : ViewModel() { private var tempFile: File? = null fun createOrGetTempFile(): File { return tempFile ?: File.createTempFile("temp", null).also { tempFile = it } } }
Java
class TempFileViewModel extends ViewModel { private File tempFile = null; public TempFileViewModel() { } @NonNull public File createOrGetTempFile() { if (tempFile == null) { tempFile = File.createTempFile("temp", null); } return tempFile; } }
لضمان عدم فقدان الملف المؤقت عند إنهاء عملية النشاط
واستعادته لاحقًا، بإمكان TempFileViewModel
استخدام SavedStateHandle
للاحتفاظ ببياناته. للسماح لـ TempFileViewModel
بحفظ بياناته، نفِّذ SavedStateProvider
واضبطها كمقدّم خدمة في SavedStateHandle
من ViewModel
:
Kotlin
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 } } }
Java
class TempFileViewModel extends ViewModel { private File tempFile = null; public TempFileViewModel(SavedStateHandle savedStateHandle) { savedStateHandle.setSavedStateProvider("temp_file", new TempFileSavedStateProvider()); } @NonNull public File createOrGetTempFile() { if (tempFile == null) { tempFile = File.createTempFile("temp", null); } return tempFile; } private class TempFileSavedStateProvider implements SavedStateRegistry.SavedStateProvider { @NonNull @Override public Bundle saveState() { Bundle bundle = new Bundle(); if (tempFile != null) { bundle.putString("path", tempFile.getAbsolutePath()); } return bundle; } } }
لاستعادة بيانات File
عند عودة المستخدم، يمكنك استرداد temp_file
Bundle
من SavedStateHandle
. هذه السمة هي السمة Bundle
نفسها المقدَّمة من
saveTempFile()
والتي تحتوي على المسار المطلق. يمكن بعد ذلك استخدام المسار المطلق لإنشاء مثيل File
جديد.
Kotlin
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 } } }
Java
class TempFileViewModel extends ViewModel { private File tempFile = null; public TempFileViewModel(SavedStateHandle savedStateHandle) { Bundle tempFileBundle = savedStateHandle.get("temp_file"); if (tempFileBundle != null) { tempFile = TempFileSavedStateProvider.restoreTempFile(tempFileBundle); } savedStateHandle.setSavedStateProvider("temp_file", new TempFileSavedStateProvider()); } @NonNull public File createOrGetTempFile() { if (tempFile == null) { tempFile = File.createTempFile("temp", null); } return tempFile; } private class TempFileSavedStateProvider implements SavedStateRegistry.SavedStateProvider { @NonNull @Override public Bundle saveState() { Bundle bundle = new Bundle(); if (tempFile != null) { bundle.putString("path", tempFile.getAbsolutePath()); } return bundle; } @Nullable private static File restoreTempFile(Bundle bundle) { if (bundle.containsKey("path") { return File(bundle.getString("path")); } return null; } } }
SaveStateHandle في الاختبارات
لاختبار ViewModel
الذي يأخذ SavedStateHandle
كتبعية، أنشئ مثيل SavedStateHandle
جديدًا بقيم الاختبار التي يتطلبها وقم بتمريره إلى مثيل ViewModel
الذي تختبره.
Kotlin
class MyViewModelTest { private lateinit var viewModel: MyViewModel @Before fun setup() { val savedState = SavedStateHandle(mapOf("someIdArg" to testId)) viewModel = MyViewModel(savedState = savedState) } }
مراجع إضافية
لمزيد من المعلومات حول وحدة "الحالة المحفوظة" لـ ViewModel
، يُرجى الاطّلاع على المراجع التالية.
الدروس التطبيقية حول الترميز
أفلام مُقترَحة لك
- ملاحظة: يتم عرض نص الرابط عند إيقاف JavaScript.
- حفظ حالات واجهة المستخدم
- استخدام عناصر البيانات القابلة للتتبّع
- إنشاء نماذج عرض باستخدام تبعيات