מודול Saved State ל-ViewModel (Views) בארגז הכלים Android Jetpack.
מושגים והטמעה ב-Jetpack פיתוח נייטיב
כמו שצוין במאמר בנושא שמירת מצבי ממשק המשתמש, אובייקטים של ViewModel יכולים לטפל בשינויים בהגדרות, כך שלא צריך לדאוג לגבי מצב ברוטציות או במקרים אחרים. עם זאת, אם אתם צריכים לטפל בסיום תהליך שהמערכת יזמה, כדאי להשתמש ב-API SavedStateHandle כגיבוי.
מצב ממשק המשתמש בדרך כלל מאוחסן באובייקטים של ViewModel או מתייחס אליהם, ולא לפעילויות. לכן, השימוש ב-onSaveInstanceState() דורש קוד boilerplate מסוים, שמודול המצב השמור יכול לטפל בו בשבילכם.
כשמשתמשים במודול הזה, ViewModel אובייקטים מקבלים אובייקט SavedStateHandle דרך הקונסטרוקטור שלו. האובייקט הזה הוא מיפוי של מפתח וערך שמאפשר לכם לכתוב אובייקטים ולשלוף אותם מהמצב השמור. הערכים האלה נשמרים גם אחרי שהמערכת מפסיקה את התהליך, והם ממשיכים להיות זמינים דרך אותו אובייקט.
המצב השמור מקושר לערימת המשימות. אם עורמים את המשימות, המצב השמור נעלם. זה יכול לקרות כשמבצעים עצירה מאולצת של אפליקציה, כשמסירים את האפליקציה מתפריט האפליקציות האחרונות או כשמפעילים מחדש את המכשיר. במקרים כאלה, מחסנית המשימות נעלמת ואי אפשר לשחזר את המידע במצב השמור. בתרחישים של סגירת ממשק המשתמש על ידי המשתמש, המצב שנשמר לא משוחזר. בתרחישים של פעולות שמתבצעות על ידי המערכת, זה קורה.
הגדרה
החל מ-Fragment 1.2.0 או מהתלות הטרנזיטיבית שלו Activity 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.
עבודה עם SavedStateHandle
המחלקות SavedStateHandle הן מיפוי של מפתח וערך שמאפשר לכתוב נתונים במצב השמור ולקבל מהם נתונים באמצעות השיטות set() ו-get().
באמצעות SavedStateHandle, ערך השאילתה נשמר גם אחרי שהתהליך מסתיים. כך המשתמש רואה את אותה קבוצת נתונים מסוננים לפני ואחרי השחזור, בלי שהפעילות או הפריט יצטרכו לשמור, לשחזר ולהעביר את הערך הזה בחזרה אל ViewModel באופן ידני.
SavedStateHandle יש גם שיטות אחרות שאפשר לצפות להן כשמבצעים אינטראקציה עם מפה של זוגות מפתח/ערך:
-
contains(String key)– בודקת אם יש ערך למפתח הנתון. -
remove(String key)– מסיר את הערך של המפתח הנתון. -
keys()– מחזירה את כל המפתחות שכלולים ב-SavedStateHandle.
בנוסף, אפשר לאחזר ערכים מ-SavedStateHandle באמצעות מאגר נתונים גלויים. רשימת הסוגים הנתמכים:
LiveData
אחזור ערכים מ-SavedStateHandle שעטופים ב-observable 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); } }
סוגים נתמכים
הנתונים שנשמרים ב-SavedStateHandle נשמרים ומשוחזרים כ-Bundle, יחד עם שאר savedInstanceState של הפעילות או של הקטע.
שמירת מחלקות שלא ניתן להעביר
אם מחלקה לא מיישמת את Parcelable או Serializable ואי אפשר לשנות אותה כדי ליישם אחד מהממשקים האלה, אי אפשר לשמור ישירות מופע של המחלקה הזו ב-SavedStateHandle.
החל מ-Lifecycle 2.3.0-alpha03, SavedStateHandle מאפשר לשמור כל אובייקט על ידי מתן לוגיקה משלכם לשמירה ולשחזור של האובייקט כ-Bundle באמצעות השיטה setSavedStateProvider().
SavedStateRegistry.SavedStateProvider הוא ממשק שמגדיר שיטה אחת של saveState() שמחזירה Bundle שמכיל את המצב שרוצים לשמור. כשהרכיב SavedStateHandle מוכן לשמור את המצב שלו, הוא קורא ל-saveState() כדי לאחזר את Bundle מ-SavedStateProvider ושומר את Bundle עבור המפתח המשויך.
לדוגמה, אפליקציה שמבקשת תמונה מאפליקציית המצלמה באמצעות intent 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; } } }
מומלץ בשבילך
- הערה: טקסט הקישור מוצג כש-JavaScript מושבת
- שמירת מצבי ממשק המשתמש
- עבודה עם אובייקטים של נתונים גלויים
- יצירת ViewModels עם תלויות