Android State Changes - State Handling in ViewModel
ในปัจจุบัน ViewModel เป็นหนึ่งใน Component ที่นักพัฒนานิยมใช้งานกัน เพื่อให้ App Architecture ของแอปเป็นไปตามที่ทีมแอนดรอยด์แนะนำ
แต่รู้หรือไม่ว่า ViewModel ก็ต้องทำ State Handling ในบางกรณีเหมือนกันนะ
บทความในชุดเดียวกัน
- Introduction
- Configuration Changes
- Process Recreation
- State Handing in Activity
- State Handing in Fragment
- State Handing in View
- State Handing in ViewModel [Now Reading]
- State Handing in Compose
ViewModel ก็ถูกทำลายเพราะ State Changes ได้
สิ่งหนึ่งที่นักพัฒนาแอนดรอยด์มักจะเข้าใจกันอย่างไม่ถูกต้อง ก็คือการเชื่อว่าถ้าเก็บข้อมูลไว้ใน ViewModel จะไม่มีทางหายเหมือนกับการเก็บข้อมูลไว้ใน Activity, Fragment, View, หรือ Compose
ซึ่งความเชื่อนี้ถูกต้องครึ่งหนึ่ง เพราะ ViewModel ไม่ถูกทำลายตอนเกิด Configuration Changes แต่ถ้าเกิด Process Recreation ก็จะทำให้ ViewModel ถูกทำลายอยู่ดี และส่งผลให้ข้อมูลที่เก็บไว้ในนั้นหายไปด้วย
ดังนั้นเพื่อไม่ให้ข้อมูลที่อยู่ใน ViewModel หายไปในตอน Process Recreation นักพัฒนาก็จะต้องทำ State Handling ให้กับข้อมูลเหล่านั้นอยู่ดี
SavedStateHandle ตัวช่วยสำหรับ State Handling ใน ViewModel
ในกรณีที่มีข้อมูลที่เก็บไว้ใน ViewModel และเกิดขึ้นในระหว่างการทำงานของ ViewModel และต้องการให้ข้อมูลยังคงอยู่และทำงานต่อได้อย่างถูกต้องหลังจากเกิด Process Recreation นักพัฒนาจะต้องทำ State Handling ใน ViewModel โดยใช้สิ่งที่เรียกว่า "SavedStateHandle"
โดยนักพัฒนาจะต้องเพิ่ม SavedStateHandle เป็น Constructor Parameter ของ ViewModel ที่ต้องการทำ State Handling แบบนี้
// HomeViewModel.kt
class HomeViewModel(private val state: SavedStateHandle) : ViewModel() {
/* ... */
}
การเพิ่ม SavedStateHandle เป็น Constructor Parameter ของ ViewModel แบบนี้จะไม่ส่งผลกับการสร้าง ViewModel ใน Activity หรือ Fragment ด้วยคำสั่ง viewModels()
เพราะข้างในคำสั่งดังกล่าวจะมี SavedStateViewModelFactory เพื่อจัดการกับ SavedStateHandle ให้โดยอัตโนมัติ
// HomeActivity.kt
class HomeActivity : AppCompatActivity() {
private val viewModel: HomeViewModel by viewModels()
/* ... */
}
// HomeFragment.kt
class HomeFragment : Fragment() {
private val viewModel: HomeViewModel by viewModels()
/* ... */
}
หรือถ้าใช้ Dependency Injection อย่าง Hilt หรือ Service Locator อย่าง Koin จะมีวิธีรองรับการสร้าง ViewModel ที่มี SavedStateHandle ให้อยู่แล้ว
การทำ State Handling ด้วย SavedStateHandle
หัวใจสำคัญของการทำ State Handling ใน ViewModel ก็คือการเก็บและคืนข้อมูลที่อยู่ใน SavedStateHandle โดยที่นักพัฒนาสามารถนำ SavedStateHandle มาใช้งานร่วมกับโค้ดที่มีอยู่แล้วในรูปแบบนี้ได้เลย
// HomeViewModel.kt
class HomeViewModel(state: SavedStateHandle) : ViewModel() {
private var currentUser: User?
get() = state.get("user")
set(value) = state.set("user", value)
/* ... */
}
ใน SavedStateHandle จะมีคำสั่งอย่าง set
และ get
เพื่อให้นักพัฒนาเก็บข้อมูลและคืนค่าเหล่านั้นกลับมาด้วยรูปแบบของ Key-value ที่คล้ายกับ Bundle โดยประเภทของข้อมูลที่ SavedStateHandle รองรับ ก็จะเหมือนกับ Bundle ทั้งหมด ตั้งแต่ Primitive Data Type, Serializable, Parcelable, ไปจนถึง Bundle เลย
ใช้ SavedStateHandle ร่วมกับ Observable Data Holder ก็ได้
ในกรณีที่ ViewModel มีการใช้ LiveData, StateFlow ก็สามารถใช้งานร่วมกับ SavedStateHandle ได้เช่นกัน
ใช้ร่วมกับ LiveData
ใช้คำสั่ง getLiveData(...)
ที่อยู่ใน SavedStateHandle เพื่อดึงข้อมูลที่ต้องการให้อยู่ในรูปของ LiveData และเมื่อต้องการเปลี่ยนแปลงข้อมูลดังกล่าวก็ให้ทำใน SavedStateHandle แทนที่จะทำใน LiveData โดยตรง
// HomeViewModel.kt
class HomeViewModel(private val state: SavedStateHandle) : ViewModel() {
val name: LiveData<String?> = state.getLiveData("name", null)
fun updateName(name: String) {
state["name"] = name
}
}
เมื่อค่าใน SavedStateHandle มีการเปลี่ยนแปลง LiveData ก็จะได้ข้อมูลล่าสุดในทันที
ใช้ร่วมกับ StateFlow
ใช้คำสั่ง getStateFlow(...)
ที่อยู่ใน SavedStateHandle เพื่อดึงข้อมูลที่ต้องการให้อยู่ในรูปของ StateFlow และเมื่อต้องการเปลี่ยนแปลงข้อมูลดังกล่าวก็ให้ทำใน SavedStateHandle แทนที่จะทำใน StateFlow โดยตรง
// HomeViewModel.kt
class HomeViewModel(private val state: SavedStateHandle) : ViewModel() {
val name: StateFlow<String?> = state.getStateFlow("name", null)
fun updateName(name: String) {
state["name"] = name
}
}
สรุป
"ViewModel ไม่ถูกทำลายเมื่อเกิด State Changes" นั้นไม่ได้ถูกต้อง 100% เพราะไม่ถูกทำลายแค่ตอน Configuration Changes เท่านั้น แต่ถ้าถ้าเกิด Process Recreation ก็จะทำให้ข้อมูลใน ViewModel หายอยู่ดี
ดังนั้นนักพัฒนาควรใช้ความสามารถของ SavedStateHandle ที่ออกแบบมาสำหรับการทำ State Handling ใน ViewModel โดยเฉพาะ เพื่อเก็บข้อมูลเหล่านั้นในตอนที่เกิด State Changes เพื่อทำให้ ViewModel ทำงานต่อได้อย่างถูกต้อง