โมดูลสถานะที่บันทึกไว้สำหรับ ViewModel ส่วนหนึ่งของ Android Jetpack
ดังที่กล่าวไว้ในการบันทึกสถานะ UI ออบเจ็กต์ ViewModel สามารถจัดการ
การเปลี่ยนแปลงการกำหนดค่าได้ คุณจึงไม่ต้องกังวลเกี่ยวกับสถานะในการหมุน
หรือกรณีอื่นๆ อย่างไรก็ตาม หากคุณต้องจัดการกระบวนการที่ระบบเริ่มต้น
การสิ้นสุด คุณอาจต้องใช้ SavedStateHandle API เป็นข้อมูลสำรอง
โดยปกติแล้ว สถานะ UI จะจัดเก็บหรืออ้างอิงในออบเจ็กต์ ViewModel ดังนั้นการใช้ rememberSaveable ใน Compose จึงต้องมีบอยเลอร์เพลตบางอย่างที่โมดูลสถานะที่บันทึกไว้สามารถจัดการให้คุณได้
เมื่อใช้โมดูลนี้ ออบเจ็กต์ ViewModel จะได้รับออบเจ็กต์ SavedStateHandle ผ่านตัวสร้าง ออบเจ็กต์นี้คือแผนที่คีย์-ค่าที่ช่วยให้คุณ
เขียนและเรียกออบเจ็กต์ไปยังและจากสถานะที่บันทึกไว้ได้ ค่าเหล่านี้
จะยังคงอยู่หลังจากที่ระบบปิดกระบวนการและยังคงใช้ได้
ผ่านออบเจ็กต์เดียวกัน
สถานะที่บันทึกไว้จะเชื่อมโยงกับสแต็กงาน หากสแต็กงานหายไป สถานะที่บันทึกไว้ก็จะหายไปด้วย ซึ่งอาจเกิดขึ้นเมื่อบังคับหยุดแอป นำแอปออกจากเมนู "ล่าสุด" หรือรีบูตอุปกรณ์ ในกรณีเช่นนี้ งาน สแต็กจะหายไปและคุณจะกู้คืนข้อมูลในสถานะที่บันทึกไว้ไม่ได้ ในสถานการณ์การปิดสถานะ UI ที่ผู้ใช้เริ่ม ระบบจะไม่คืนค่าสถานะที่บันทึกไว้ ในสถานการณ์ที่ระบบเริ่มต้น
ตั้งค่า
หากต้องการใช้ 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 และ DSL viewModelFactory
การทำงานกับ SavedStateHandle
SavedStateHandle class คือแผนที่คีย์-ค่าที่ช่วยให้คุณเขียนและ
เรียกข้อมูลไปยังและจากสถานะที่บันทึกไว้ผ่านเมธอด set() และ
get()
การใช้ SavedStateHandle จะช่วยให้ค่าการค้นหายังคงอยู่เมื่อการประมวลผลสิ้นสุดลง เพื่อให้มั่นใจว่าผู้ใช้จะเห็นชุดข้อมูลที่กรองแล้วชุดเดียวกันทั้งก่อนและหลัง การสร้างใหม่โดยไม่ต้องให้กิจกรรมหรือ Fragment บันทึก กู้คืน และส่งต่อค่านั้นกลับไปยัง ViewModel ด้วยตนเอง
SavedStateHandle ยังมีเมธอดอื่นๆ ที่คุณอาจคาดหวังเมื่อโต้ตอบ
กับแมปคีย์-ค่าด้วย
contains(String key)- ตรวจสอบว่ามีค่าสำหรับคีย์ที่ระบุหรือไม่remove(String key)- นำค่าสำหรับคีย์ที่ระบุออกkeys()- แสดงคีย์ทั้งหมดที่อยู่ในSavedStateHandle
นอกจากนี้ คุณยังดึงค่าจาก SavedStateHandle ได้โดยใช้ที่เก็บข้อมูลที่ได้รับอนุญาตให้สังเกตพฤติกรรมผู้ใช้ได้ รายการประเภทที่รองรับมีดังนี้
StateFlow
คุณเรียกค่าจาก SavedStateHandle ที่อยู่ใน StateFlow
observable ได้ คุณสามารถเลือกสตรีมแบบอ่านอย่างเดียวหรือสตรีมที่เปลี่ยนแปลงได้ ทั้งนี้ขึ้นอยู่กับว่าคุณต้องเปลี่ยนค่าโดยตรงหรือไม่
getStateFlow(): ใช้ในกรณีที่คุณต้องการอ่านสถานะเท่านั้น เมื่อคุณ อัปเดตค่าของคีย์ที่อื่นในSavedStateHandle, StateFlow จะได้รับค่าใหม่ ซึ่งเหมาะอย่างยิ่งเมื่อคุณต้องการแสดงสตรีมแบบอ่านอย่างเดียว และแปลงสตรีมโดยใช้ตัวดำเนินการ FlowgetMutableStateFlow(): ใช้ตัวเลือกนี้หากคุณต้องการทั้งสิทธิ์ในการอ่านและเขียน การอัปเดต.valueของMutableStateFlowที่ส่งคืนจะอัปเดต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 Serialization
สำหรับสถานะ UI ที่ซับซ้อน คุณสามารถใช้ตัวแทนพร็อพเพอร์ตี้ saved ร่วมกับ
KotlinX Serialization ได้ การมอบสิทธิ์นี้ช่วยให้คุณบันทึกคลาสข้อมูล @Serializable
ที่กำหนดเองลงใน SavedStateHandle ได้โดยตรง ซึ่งจะรักษาสถานะของ ViewModel ไว้เมื่อการประมวลผลสิ้นสุดลง เพื่อให้ UI ของ Compose สามารถกู้คืนสถานะได้อย่างราบรื่นเมื่อมีการสร้างใหม่
หากต้องการใช้ ให้ใส่คำอธิบายประกอบคลาสข้อมูลด้วย @Serializable และใช้ saved
delegate ใน 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 support
หากรัฐของคุณใช้ API ของ Saver Compose แทน KotlinX
Serialization อาร์ติแฟกต์ 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+) |
หากคลาสไม่ได้ขยายคลาสใดคลาสหนึ่งในรายการข้างต้น ให้พิจารณาทำให้คลาสสามารถส่งผ่านได้โดยการเพิ่มคำอธิบายประกอบ @Parcelize ของ Kotlin หรือการใช้ Parcelable โดยตรง
การบันทึกคลาสที่ส่งผ่านไม่ได้
หากคลาสไม่ได้ใช้ 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 จะห่อหุ้ม
ตรรกะสำหรับการสร้างไฟล์ชั่วคราวดังกล่าว
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 เป็นทรัพยากร Dependency ให้สร้างอินสแตนซ์ใหม่ของ SavedStateHandle ด้วยค่าทดสอบที่ต้องใช้ แล้วส่งไปยังอินสแตนซ์ ViewModel ที่คุณกำลังทดสอบ
class MyViewModelTest { private lateinit var viewModel: MyViewModel @Before fun setup() { val savedState = SavedStateHandle(mapOf("someIdArg" to testId)) viewModel = MyViewModel(savedState = savedState) } }
แหล่งข้อมูลเพิ่มเติม
ดูข้อมูลเพิ่มเติมเกี่ยวกับโมดูลสถานะที่บันทึกไว้สำหรับ ViewModel ได้ที่แหล่งข้อมูลต่อไปนี้
Codelab
ดูเนื้อหา
แนะนำสำหรับคุณ
- หมายเหตุ: ข้อความลิงก์จะแสดงเมื่อ JavaScript ปิดอยู่
- บันทึกสถานะ UI
- ทำงานกับออบเจ็กต์ข้อมูลที่ได้รับอนุญาตให้สังเกตพฤติกรรมผู้ใช้ได้
- สร้าง ViewModel ที่มี Dependency