وحدة "الحالة المحفوظة" لـ ViewModel جزء من Android Jetpack.
كما هو موضّح في مقالة
حفظ حالات واجهة المستخدم، يمكن للكائنات
ViewModel
التعامل مع
تغييرات الإعدادات، لذا لا داعي للقلق بشأن الحالة في عمليات التدوير
أو الحالات الأخرى. ومع ذلك، إذا كنت بحاجة إلى معالجة عملية
الإنهاء التي يبدأها النظام، يمكنك استخدام واجهة برمجة التطبيقات SavedStateHandle
كخيار احتياطي.
يتم عادةً تخزين حالة واجهة المستخدم أو الإشارة إليها في عناصر ViewModel
وليس في
الأنشطة، لذا يتطلّب استخدام onSaveInstanceState()
أو rememberSaveable
بعض
النص الجاهز الذي يمكن أن تتعامل معه وحدة الحالة المحفوظة
نيابةً عنك.
عند استخدام هذه الوحدة، تتلقّى عناصر ViewModel
عنصرًا
SavedStateHandle
من خلال الدالة الإنشائية. هذا العنصر هو خريطة مفاتيح وقيم تتيح لك قراءة الكائنات واستعادتها من الحالة المحفوظة وإليها. وتظلّ هذه القيم
مستمرّة بعد أن يوقف النظام العملية وتظلّ متاحة
من خلال الكائن نفسه.
تكون الحالة المحفوظة مرتبطة بحزمة المهام. إذا اختفت حزمة المهام، ستختفي الحالة المحفوظة أيضًا. ويمكن أن يحدث ذلك عند فرض إيقاف أحد التطبيقات أو إزالة التطبيق من قائمة التطبيقات المستخدَمة مؤخرًا أو إعادة تشغيل الجهاز. وفي هذه الحالات، تختفي ملفّات مهمة المعالجة ولا يمكنك استعادة المعلومات في الحالة المحفوظة. في سيناريوهات إغلاق حالة واجهة المستخدم التي بدأها المستخدم ، لا تتم استعادة الحالة المحفوظة. في سيناريوهات البدء من النظام ، يكون ذلك صحيحًا.
ضبط إعدادات الجهاز
اعتبارًا من الإصدار الإصدار 1.2.0 من Fragment
أو الإصدار الإصدار 1.1.0 من Activity الذي يعتمد عليه، يمكنك قبول
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
.
العمل مع SavedStateHandle
فئة SavedStateHandle
هي خريطة مفاتيح وقيم تتيح لك كتابة البيانات واستعادتها من الحالة المحفوظة وإليها من خلال الطريقتَين
set()
وget()
.
باستخدام SavedStateHandle
، يتم الاحتفاظ بقيمة الطلب على مستوى عملية الإغلاق،
ما يضمن ظهور المجموعة نفسها من البيانات التي تمّت فلترتها قبل
إعادة الإنشاء وبعدها بدون الحاجة إلى حفظ هذه القيمة وإعادتها
وإعادة توجيهها يدويًا إلى ViewModel
في النشاط أو المقتطف.
تتضمّن SavedStateHandle
أيضًا طرقًا أخرى قد تتوقّعها عند التفاعل
مع خريطة مفاتيح وقيم:
-
contains(String key)
: للتحقّق مما إذا كانت هناك قيمة للمفتاح المحدّد -
remove(String key)
- تزيل قيمة المفتاح المحدّد. -
keys()
- لعرض جميع المفاتيح المضمّنة فيSavedStateHandle
بالإضافة إلى ذلك، يمكنك استرداد القيم من SavedStateHandle
باستخدام
حامل بيانات قابل للرصد. في ما يلي قائمة الأنواع المتوافقة:
LiveData
استرداد القيم من SavedStateHandle
التي تكون ملفوفة في ملف قابل للملاحظة
LiveData
باستخدام
getLiveData()
عند تعديل قيمة المفتاح، يتلقّى LiveData
القيمة الجديدة. في معظم
الأحيان، يتم ضبط القيمة بسبب تفاعلات المستخدِم، مثل إدخال طلب بحث لfiltrare قائمة البيانات. ويمكن بعد ذلك استخدام هذه القيمة المعدَّلة للقيام بعملية
تحويل 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
القيمة الجديدة. في معظم
الأحيان، قد تضبط القيمة استنادًا إلى تفاعلات المستخدِم، مثل إدخال query لتصفية قائمة بالبيانات. يمكنك بعد ذلك تحويل هذه القيمة المعدَّلة
باستخدام عوامل تشغيل تدفق أخرى.
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 } }
إتاحة حالة ميزة "الإنشاء" التجريبية
.يوفّر العنصر lifecycle-viewmodel-compose
واجهات برمجة التطبيقات التجريبية
saveable
التي تتيح إمكانية التشغيل التفاعلي بين SavedStateHandle
وSaver
في أداة Compose، بحيث يمكن أيضًا حفظ أي 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
intent، مع إدخال ملف مؤقت للمكان الذي يجب أن تخزِّن فيه الكاميرا
الصورة. يُحاط 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; } } }
SavedStateHandle في الاختبارات
لاختبار 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 غير مفعّلة.
- حفظ حالات واجهة المستخدم
- العمل مع عناصر البيانات القابلة للتتبّع
- إنشاء نماذج ViewModel مع التبعيات