ภาพรวม ViewModel ส่วนหนึ่งของ Android Jetpack
คลาส ViewModel คือตรรกะทางธุรกิจหรือตัวเก็บสถานะระดับสกรีน โดยจะแสดงสถานะต่อ UI และห่อหุ้มตรรกะทางธุรกิจที่เกี่ยวข้อง
ข้อได้เปรียบหลักคือการแคชสถานะและคงสถานะไว้เมื่อมีการเปลี่ยนแปลงการกำหนดค่า ซึ่งหมายความว่า UI ไม่ต้องดึงข้อมูลอีกครั้ง
เมื่อไปยังส่วนต่างๆ ระหว่างกิจกรรม หรือเมื่อมีการเปลี่ยนแปลงการกำหนดค่า เช่น
เมื่อหมุนหน้าจอ
ดูข้อมูลเพิ่มเติมเกี่ยวกับผู้ถือครองสถานะได้ที่คำแนะนำสำหรับผู้ถือครองสถานะ ในทำนองเดียวกัน หากต้องการข้อมูลเพิ่มเติมเกี่ยวกับเลเยอร์ UI โดยทั่วไป โปรดดูคำแนะนำเกี่ยวกับเลเยอร์ UI
สิทธิประโยชน์ของ ViewModel
ทางเลือกแทน ViewModel คือคลาสธรรมดาที่เก็บข้อมูลที่คุณแสดง ใน UI ซึ่งอาจกลายเป็นปัญหาเมื่อไปยังส่วนต่างๆ ระหว่างกิจกรรมหรือ ปลายทางการนำทาง การทำเช่นนี้จะทำลายข้อมูลดังกล่าวหากคุณไม่ได้จัดเก็บข้อมูลโดยใช้กลไกสถานะอินสแตนซ์ที่บันทึกไว้ ViewModel มี API ที่สะดวก สำหรับการคงอยู่ของข้อมูลซึ่งช่วยแก้ปัญหานี้ได้
หรือสำหรับผู้ถือครองสถานะอย่างเดียว Compose มีretainความสามารถที่
ช่วยให้คลาสธรรมดารอดพ้นจากการเปลี่ยนแปลงการกำหนดค่าได้โดยไม่ต้องมีโครงสร้างพื้นฐานทั้งหมดของ ViewModel แม้ว่าทั้ง 2 กลไกจะช่วยรักษาสถานะได้ แต่โดยทั่วไปแล้วการระบุ ViewModel ให้กับอินสแตนซ์ที่เก็บไว้จะปลอดภัยกว่า การระบุอินสแตนซ์ที่เก็บไว้ให้กับ ViewModel เนื่องจากวงจรและลักษณะการทำงานในการล้างข้อมูลแตกต่างกัน
ประโยชน์หลักของคลาส ViewModel มี 2 ประการ ได้แก่
- ซึ่งช่วยให้คุณคงสถานะ UI ไว้ได้
- ซึ่งให้สิทธิ์เข้าถึงตรรกะทางธุรกิจ
ความต่อเนื่อง
ViewModel ช่วยให้คงอยู่ได้ทั้งผ่านสถานะที่ ViewModel เก็บไว้และ การดำเนินการที่ ViewModel เรียกใช้ การแคชนี้หมายความว่าคุณไม่จำเป็นต้องดึงข้อมูลอีกครั้งผ่านการเปลี่ยนแปลงการกำหนดค่าทั่วไป เช่น การหมุนหน้าจอ
ขอบเขต
เมื่อสร้างอินสแตนซ์ ViewModel คุณจะส่งออบเจ็กต์ที่ใช้
อินเทอร์เฟซ ViewModelStoreOwner ไปยัง ViewModel ซึ่งอาจเป็นปลายทางการนำทาง กราฟการนำทาง กิจกรรม หรือประเภทอื่นๆ ที่ใช้
อินเทอร์เฟซ นอกจากนี้ คุณยังกำหนดขอบเขต ViewModel ให้กับ Composable ได้โดยตรงโดยใช้ API rememberViewModelStoreOwner
จากนั้น ViewModel จะกำหนดขอบเขตเป็น Lifecycle ของ
ViewModelStoreOwner โดยจะยังคงอยู่ในหน่วยความจำจนกว่าจะViewModelStoreOwner
หายไปอย่างถาวร (เช่น เมื่อเจ้าของ Composable ออกจาก Composition)
คลาสต่างๆ เป็นคลาสย่อยโดยตรงหรือโดยอ้อมของอินเทอร์เฟซ ViewModelStoreOwner
คลาสย่อยโดยตรงคือ
ComponentActivity และ NavBackStackEntry
ดูรายการคลาสย่อยทางอ้อมทั้งหมดได้ในViewModelStoreOwnerการอ้างอิง หากต้องการกำหนดขอบเขต ViewModel ให้กับแต่ละรายการ
ใน LazyList หรือ Pager ให้ใช้ rememberViewModelStoreProvider() เพื่อยกระดับ
การจัดการเจ้าของไปยังระดับบน
เมื่อกิจกรรมโฮสต์มีการเปลี่ยนแปลงการกำหนดค่า งานแบบอะซิงโครนัสจะดำเนินต่อไป ใน ViewModel ไม่ว่าจะอยู่ในขอบเขตของกิจกรรมหรือ Composable ที่เฉพาะเจาะจง ซึ่งเป็นกุญแจสำคัญในการสร้างความคงทน
ดูข้อมูลเพิ่มเติมได้ที่ส่วนวงจรของ ViewModel ที่ตามมา API การกำหนดขอบเขต ViewModel และคำแนะนำเกี่ยวกับการย้ายสถานะสำหรับ Jetpack Compose
SavedStateHandle
SavedStateHandle ช่วยให้คุณคงข้อมูลไว้ได้ไม่เพียงแค่ผ่านการเปลี่ยนแปลงการกำหนดค่า เท่านั้น แต่ยังคงไว้ได้แม้ว่ากระบวนการจะสิ้นสุดลง กล่าวคือ ช่วยให้คุณคงสถานะ UI ไว้ได้แม้ว่าผู้ใช้จะปิดแอปและเปิดอีกครั้งในภายหลัง
ดูข้อมูลเพิ่มเติมเกี่ยวกับการบันทึกสถานะ UI ได้ที่บันทึกสถานะ UI ใน Compose
การเข้าถึงตรรกะทางธุรกิจ
แม้ว่าตรรกะทางธุรกิจส่วนใหญ่จะอยู่ในเลเยอร์ข้อมูล แต่เลเยอร์ UI ก็อาจมีตรรกะทางธุรกิจได้เช่นกัน กรณีนี้อาจเกิดขึ้นเมื่อ รวมข้อมูลจากที่เก็บหลายแห่งเพื่อสร้างสถานะ UI ของหน้าจอ หรือเมื่อ ข้อมูลประเภทหนึ่งๆ ไม่ต้องใช้ชั้นข้อมูล
ViewModel เป็นที่ที่เหมาะสมในการจัดการตรรกะทางธุรกิจในเลเยอร์ UI ViewModel ยังมีหน้าที่จัดการเหตุการณ์และมอบหมายให้เลเยอร์อื่นๆ ในลำดับชั้นเมื่อต้องใช้ตรรกะทางธุรกิจเพื่อแก้ไขข้อมูลแอปพลิเคชัน
ใช้ ViewModel
ต่อไปนี้คือตัวอย่างการใช้งาน ViewModel สำหรับหน้าจอที่ อนุญาตให้ผู้ใช้ทอยลูกเต๋า
data class DiceUiState(
val firstDieValue: Int? = null,
val secondDieValue: Int? = null,
val numberOfRolls: Int = 0,
)
class DiceRollViewModel : ViewModel() {
// Expose screen UI state
private val _uiState = MutableStateFlow(DiceUiState())
val uiState: StateFlow<DiceUiState> = _uiState.asStateFlow()
// Handle business logic
fun rollDice() {
_uiState.update { currentState ->
currentState.copy(
firstDieValue = Random.nextInt(from = 1, until = 7),
secondDieValue = Random.nextInt(from = 1, until = 7),
numberOfRolls = currentState.numberOfRolls + 1,
)
}
}
}
จากนั้นคุณจะเข้าถึง ViewModel จาก Composable ระดับหน้าจอได้ดังนี้
import androidx.lifecycle.viewmodel.compose.viewModel
// Use the 'viewModel()' function from the lifecycle-viewmodel-compose artifact
@Composable
fun DiceRollScreen(
viewModel: DiceRollViewModel = viewModel()
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
// Update UI elements
}
ใช้โครูทีนกับ ViewModel
ViewModel มีการรองรับโครูทีนของ Kotlin โดยสามารถคงการทำงานแบบอะซิงโครนัสได้ในลักษณะเดียวกับการคงสถานะ UI
ดูข้อมูลเพิ่มเติมได้ที่ใช้ Kotlin Coroutines กับ Android Architecture Components
วงจรการใช้งานของ ViewModel
วงจรของ ViewModel จะเชื่อมโยงกับขอบเขตของ ViewModel โดยตรง ViewModel
จะยังคงอยู่ในหน่วยความจำจนกว่า ViewModelStoreOwner ที่กำหนดขอบเขตไว้
จะหายไป ซึ่งอาจเกิดขึ้นในบริบทต่อไปนี้
- ในกรณีของกิจกรรม เมื่อกิจกรรมสิ้นสุดลง
- ในกรณีของรายการการนำทาง เมื่อนำออกจาก Back Stack
- ในกรณีของ Composable เมื่อออกจาก Composition
คุณใช้
rememberViewModelStoreOwnerเพื่อกำหนดขอบเขต ViewModel โดยตรงกับ ส่วนใดก็ได้ของ UI (เช่นPagerหรือLazyList)
ซึ่งทำให้ ViewModel เป็นโซลูชันที่ยอดเยี่ยมสำหรับการจัดเก็บข้อมูลที่ยังคงอยู่ แม้จะมีการเปลี่ยนแปลงการกำหนดค่า
รูปที่ 1 แสดงสถานะวงจรต่างๆ ของกิจกรรมเมื่อมีการหมุนเวียนและสิ้นสุด ภาพยังแสดงอายุการใช้งานของ
ViewModel ข้างวงจรกิจกรรมที่เกี่ยวข้องด้วย แผนภาพนี้แสดงสถานะของกิจกรรม
โดยปกติคุณจะขอ ViewModel ในครั้งแรกที่ระบบเรียกใช้เมธอด onCreate() ของออบเจ็กต์ Activity ระบบอาจเรียกใช้
onCreate() หลายครั้งตลอดช่วงเวลาที่มีกิจกรรมอยู่ เช่น
เมื่อหมุนหน้าจออุปกรณ์ ViewModel จะมีอยู่ตั้งแต่เมื่อคุณขอ ViewModel เป็นครั้งแรกจนกว่ากิจกรรมจะเสร็จสิ้นและถูกทำลาย
การล้างทรัพยากร Dependency ของ ViewModel
ViewModel จะเรียกใช้เมธอด onCleared เมื่อ ViewModelStoreOwner ทำลาย ViewModel ในระหว่างวงจรชีวิตของ ViewModel ซึ่งจะช่วยให้คุณล้างข้อมูลงาน
หรือการอ้างอิงที่ทำตามวงจรของ ViewModel ได้
ตัวอย่างต่อไปนี้แสดงทางเลือกแทน viewModelScope
viewModelScope เป็น CoroutineScope ในตัวที่
ติดตามวงจรของ ViewModel โดยอัตโนมัติ ViewModel ใช้เพื่อ
ทริกเกอร์การดำเนินการที่เกี่ยวข้องกับธุรกิจ หากต้องการใช้ขอบเขตที่กำหนดเองแทน viewModelScope เพื่อให้ทดสอบได้ง่ายขึ้น ViewModel จะรับ CoroutineScope เป็นทรัพยากร Dependency ในเครื่องมือสร้างได้ เมื่อ
ViewModelStoreOwner ล้าง ViewModel เมื่อสิ้นสุดวงจรการทำงานแล้ว ViewModel จะยกเลิก CoroutineScope ด้วย
class MyViewModel(
private val coroutineScope: CoroutineScope =
CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
) : ViewModel() {
// Other ViewModel logic ...
override fun onCleared() {
coroutineScope.cancel()
}
}
ตั้งแต่ version 2.5 ของวงจรขึ้นไป
คุณสามารถส่งออบเจ็กต์ Closeable
อย่างน้อย 1 รายการไปยังตัวสร้างของ ViewModel ซึ่งจะปิดโดยอัตโนมัติเมื่อ
ล้างอินสแตนซ์ ViewModel
class CloseableCoroutineScope(
context: CoroutineContext = SupervisorJob() + Dispatchers.Main.immediate
) : Closeable, CoroutineScope {
override val coroutineContext: CoroutineContext = context
override fun close() {
coroutineContext.cancel()
}
}
class MyViewModel(
private val coroutineScope: CoroutineScope = CloseableCoroutineScope()
) : ViewModel(coroutineScope) {
// Other ViewModel logic ...
}
แนวทางปฏิบัติแนะนำ
ต่อไปนี้คือแนวทางปฏิบัติแนะนำที่สำคัญบางส่วนที่คุณควรปฏิบัติตามเมื่อติดตั้งใช้งาน ViewModel
- เนื่องจากการกำหนดขอบเขต ให้ใช้ ViewModel เป็นรายละเอียดการใช้งานของ ตัวเก็บสถานะระดับหน้าจอ อย่าใช้เป็นที่เก็บสถานะของคอมโพเนนต์ UI ที่นำกลับมาใช้ใหม่ได้ เช่น กลุ่มชิปหรือแบบฟอร์ม ไม่เช่นนั้น คุณจะได้รับอินสแตนซ์ ViewModel เดียวกันในการใช้งานคอมโพเนนต์ UI เดียวกันภายใต้ ViewModelStoreOwner เดียวกัน เว้นแต่คุณจะใช้คีย์ ViewModel ที่ชัดเจนต่อชิป
- ViewModel ไม่ควรรู้รายละเอียดการใช้งาน UI ตั้งชื่อเมธอดที่ ViewModel API แสดงและชื่อฟิลด์สถานะ UI ให้เป็นชื่อทั่วไปมากที่สุด ด้วยวิธีนี้ ViewModel จึงรองรับ UI ทุกประเภท ไม่ว่าจะเป็นโทรศัพท์มือถือ อุปกรณ์แบบพับได้ แท็บเล็ต หรือแม้แต่ Chromebook
- เนื่องจากมีแนวโน้มที่จะมีอายุการใช้งานนานกว่า
ViewModelStoreOwnerViewModel จึงไม่ควรเก็บการอ้างอิง API ที่เกี่ยวข้องกับวงจร เช่นContextหรือResourcesเพื่อป้องกันไม่ให้หน่วยความจำรั่ว - อย่าส่ง ViewModels ไปยังคลาส ฟังก์ชัน หรือคอมโพเนนต์ UI อื่นๆ เนื่องจากแพลตฟอร์มจัดการองค์ประกอบเหล่านี้ คุณจึงควรเก็บองค์ประกอบไว้ใกล้กับแพลตฟอร์มให้มากที่สุดเท่าที่จะทำได้ เช่น ใกล้กับกิจกรรม ฟังก์ชันที่ประกอบกันได้ระดับหน้าจอ หรือปลายทางการนำทาง ซึ่งจะช่วยป้องกันไม่ให้คอมโพเนนต์ระดับล่างเข้าถึงข้อมูลและตรรกะมากกว่าที่จำเป็น
ข้อมูลเพิ่มเติม
เมื่อข้อมูลมีความซับซ้อนมากขึ้น คุณอาจเลือกใช้คลาสแยกต่างหากเพื่อโหลดข้อมูลโดยเฉพาะ จุดประสงค์ของ ViewModel คือการห่อหุ้มข้อมูลสำหรับ
ตัวควบคุม UI เพื่อให้ข้อมูลยังคงอยู่ได้แม้จะมีการเปลี่ยนแปลงการกำหนดค่า ดูข้อมูล
เกี่ยวกับวิธีโหลด จัดเก็บ และจัดการข้อมูลเมื่อมีการเปลี่ยนแปลงการกำหนดค่าได้ที่
สถานะ UI ที่บันทึกไว้
คำแนะนำเกี่ยวกับสถาปัตยกรรมแอป Android แนะนำให้สร้างคลาสที่เก็บ เพื่อจัดการฟังก์ชันเหล่านี้
แหล่งข้อมูลเพิ่มเติม
โปรดดูข้อมูลเพิ่มเติมเกี่ยวกับViewModel คลาสได้ที่แหล่งข้อมูลต่อไปนี้
เอกสารประกอบ
ดูเนื้อหา
ตัวอย่าง
แนะนำสำหรับคุณ
- หมายเหตุ: ข้อความลิงก์จะแสดงเมื่อ JavaScript ปิดอยู่
- ใช้โครูทีน Kotlin กับคอมโพเนนต์ที่รับรู้ถึงวงจร
- บันทึกสถานะ UI
- โหลดและแสดงข้อมูลแบบแบ่งหน้า