โมดูลสถานะที่บันทึกไว้สำหรับ ViewModel เป็นส่วนหนึ่งของ Android Jetpack
ดังที่ได้กล่าวไว้ในหัวข้อการบันทึกสถานะ UI ออบเจ็กต์ ViewModel
สามารถจัดการการเปลี่ยนแปลงการกำหนดค่าได้ คุณจึงไม่ต้องกังวลเกี่ยวกับสถานะในการหมุนหรือกรณีอื่นๆ อย่างไรก็ตาม หากต้องการจัดการการสิ้นสุดกระบวนการที่ระบบเริ่ม คุณอาจต้องใช้ SavedStateHandle
API เป็นข้อมูลสำรอง
โดยทั่วไปสถานะ UI จะจัดเก็บหรืออ้างอิงในออบเจ็กต์ ViewModel
ไม่ใช่ในกิจกรรม ดังนั้นการใช้ onSaveInstanceState()
หรือ rememberSaveable
จึงต้องใช้ข้อความที่เขียนขึ้นไว้ล่วงหน้าซึ่งโมดูลสถานะที่บันทึกไว้จะจัดการให้คุณได้
เมื่อใช้โมดูลนี้ ออบเจ็กต์ ViewModel
จะได้รับออบเจ็กต์ SavedStateHandle
ผ่านคอนสตรัคเตอร์ ออบเจ็กต์นี้เป็นแผนที่คีย์-ค่าที่ช่วยให้คุณเขียนและเรียกข้อมูลออบเจ็กต์จากสถานะที่บันทึกไว้ ค่าเหล่านี้จะยังคงอยู่หลังจากที่ระบบหยุดกระบวนการและยังคงใช้งานได้ผ่านออบเจ็กต์เดียวกัน
สถานะที่บันทึกไว้จะเชื่อมโยงกับกองงาน หากกองงานหายไป สถานะที่บันทึกไว้ก็จะหายไปด้วย ซึ่งอาจเกิดขึ้นเมื่อบังคับหยุดแอป นำแอปออกจากเมนูล่าสุด หรือรีบูตอุปกรณ์ ในกรณีเช่นนี้ กองงานจะหายไปและคุณจะกู้คืนข้อมูลในสถานะที่บันทึกไว้ไม่ได้ ในสถานการณ์การปิดสถานะ UI ที่ผู้ใช้เริ่ม ระบบจะไม่กู้คืนสถานะที่บันทึกไว้ ในกรณีที่ระบบเริ่ม จะเป็นเช่นนั้น
ตั้งค่า
ตั้งแต่ Fragment 1.2.0 หรือ Activity 1.1.0 ที่เป็น transitive dependency ของ Fragment 1.2.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
ที่รวมอยู่ใน 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
มี API 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
โดยตรงไม่ได้
ตั้งแต่วงจร 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()
ที่ได้จาก
saveTempFile()
ซึ่งมีเส้นทางแบบสัมบูรณ์ จากนั้นจะใช้ Absolute Path เพื่อสร้างอินสแตนซ์ 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
เป็น Dependency ให้สร้างอินสแตนซ์ใหม่ของ 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
ได้ที่แหล่งข้อมูลต่อไปนี้
Codelabs
แนะนำสำหรับคุณ
- หมายเหตุ: ข้อความลิงก์จะแสดงเมื่อ JavaScript ปิดอยู่
- บันทึกสถานะ UI
- ทํางานกับออบเจ็กต์ข้อมูลที่สังเกตได้
- สร้าง ViewModel ที่มี Dependency