ภาพรวม ViewModel   ส่วนหนึ่งของ Android Jetpack

ลองใช้ Kotlin Multiplatform
Kotlin Multiplatform ช่วยให้แชร์ตรรกะทางธุรกิจกับแพลตฟอร์มอื่นๆ ได้ ดูวิธีตั้งค่าและทำงานกับ ViewModel ใน KMP

คลาส 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 เมื่อกิจกรรมเปลี่ยนสถานะ
รูปที่ 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
  • เนื่องจากมีแนวโน้มที่จะมีอายุการใช้งานนานกว่า ViewModelStoreOwner ViewModel จึงไม่ควรเก็บการอ้างอิง API ที่เกี่ยวข้องกับวงจร เช่น Context หรือ Resources เพื่อป้องกันไม่ให้หน่วยความจำรั่ว
  • อย่าส่ง ViewModels ไปยังคลาส ฟังก์ชัน หรือคอมโพเนนต์ UI อื่นๆ เนื่องจากแพลตฟอร์มจัดการองค์ประกอบเหล่านี้ คุณจึงควรเก็บองค์ประกอบไว้ใกล้กับแพลตฟอร์มให้มากที่สุดเท่าที่จะทำได้ เช่น ใกล้กับกิจกรรม ฟังก์ชันที่ประกอบกันได้ระดับหน้าจอ หรือปลายทางการนำทาง ซึ่งจะช่วยป้องกันไม่ให้คอมโพเนนต์ระดับล่างเข้าถึงข้อมูลและตรรกะมากกว่าที่จำเป็น

ข้อมูลเพิ่มเติม

เมื่อข้อมูลมีความซับซ้อนมากขึ้น คุณอาจเลือกใช้คลาสแยกต่างหากเพื่อโหลดข้อมูลโดยเฉพาะ จุดประสงค์ของ ViewModel คือการห่อหุ้มข้อมูลสำหรับ ตัวควบคุม UI เพื่อให้ข้อมูลยังคงอยู่ได้แม้จะมีการเปลี่ยนแปลงการกำหนดค่า ดูข้อมูล เกี่ยวกับวิธีโหลด จัดเก็บ และจัดการข้อมูลเมื่อมีการเปลี่ยนแปลงการกำหนดค่าได้ที่ สถานะ UI ที่บันทึกไว้

คำแนะนำเกี่ยวกับสถาปัตยกรรมแอป Android แนะนำให้สร้างคลาสที่เก็บ เพื่อจัดการฟังก์ชันเหล่านี้

แหล่งข้อมูลเพิ่มเติม

โปรดดูข้อมูลเพิ่มเติมเกี่ยวกับViewModel คลาสได้ที่แหล่งข้อมูลต่อไปนี้

เอกสารประกอบ

ดูเนื้อหา

ตัวอย่าง