ดูภาพรวมของโมเดล เป็นส่วนหนึ่งของ Android Jetpack

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

ดูข้อมูลเพิ่มเติมเกี่ยวกับผู้ถือรัฐได้ที่คำแนะนำสำหรับผู้ถือรัฐ ในทำนองเดียวกัน หากต้องการข้อมูลเพิ่มเติมเกี่ยวกับเลเยอร์ UI โดยทั่วไป ให้ดูที่เลเยอร์ UI คำแนะนำ

ประโยชน์ของ ViewModel

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

ประโยชน์หลักๆ ของคลาส ViewModel หลักๆ แล้วมีอยู่ 2 ประการดังนี้

  • ซึ่งช่วยให้คุณรักษาสถานะ UI ไว้ได้
  • มอบการเข้าถึงตรรกะทางธุรกิจ

ความต่อเนื่อง

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

ขอบเขต

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

ช่วงคลาสจะเป็นคลาสย่อยทางตรงหรือทางอ้อมของ อินเทอร์เฟซของ ViewModelStoreOwner คลาสย่อยโดยตรงได้แก่ ComponentActivity, Fragment และ NavBackStackEntry ดูรายการคลาสย่อยโดยอ้อมทั้งหมดได้ที่ การอ้างอิง ViewModelStoreOwner รายการ

เมื่อส่วนย่อยหรือกิจกรรมที่มีการกำหนดขอบเขต ViewModel ถูกทำลาย การทำงานแบบไม่พร้อมกันจะดำเนินต่อไปใน ViewModel ที่มีขอบเขตการทำงาน นี่คือ กุญแจสู่ความต่อเนื่อง

ดูข้อมูลเพิ่มเติมได้ที่ส่วนวงจรการใช้งาน ViewModel ด้านล่าง

สถานะแฮนเดิลที่บันทึกไว้

SavedStateHandle ให้คุณคงข้อมูลไว้ได้ ไม่ใช่แค่การกำหนดค่า เปลี่ยนแปลงไป แต่ครอบคลุมกระบวนการทำงานต่างๆ มากขึ้น กล่าวคือคุณสามารถทำให้ UI จะยังคงเดิมแม้ว่าผู้ใช้จะปิดแอปและเปิดแอปในภายหลังก็ตาม

การเข้าถึงตรรกะทางธุรกิจ

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

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

Jetpack Compose

เมื่อใช้ Jetpack Compose, ViewModel เป็นวิธีหลักในการแสดง UI ของหน้าจอ เป็น Composable ในแอปแบบผสม กิจกรรมและ Fragment จะโฮสต์ ฟังก์ชันที่ประกอบกันได้ นี่เป็นการเปลี่ยนแปลงจากแนวทางที่ผ่านมา ซึ่งไม่ ง่ายและสะดวกในการสร้าง UI ที่นำกลับมาใช้ใหม่ได้ด้วยกิจกรรมและ แฟรกเมนต์ใหม่ ซึ่งทำให้มีการใช้งานมากขึ้นมากในฐานะตัวควบคุม UI

สิ่งสำคัญที่สุดที่ต้องคำนึงถึงเมื่อใช้ ViewModel กับการเขียนคือ ว่าคุณไม่สามารถกำหนดขอบเขต ViewModel ให้กับ Composable ได้ นั่นเป็นเพราะ Composable ไม่ใช่ ViewModelStoreOwner Composable เดียวกันสองอินสแตนซ์ใน Composable หรือ Composable ที่ต่างกัน 2 รายการเข้าถึง ViewModel เดียวกัน ภายใต้ ViewModelStoreOwner เดียวกันจะได้รับอินสแตนซ์ เดียวกันของ ViewModel ซึ่งมักไม่ใช่ลักษณะการทำงานตามที่คาดไว้

หากต้องการรับประโยชน์ของ ViewModel ใน Compose ให้โฮสต์แต่ละหน้าจอเป็น Fragment หรือกิจกรรม หรือใช้ Compose Navigation และใช้ ViewModels ใน Composable ให้อยู่ใกล้กับจุดหมายของการนำทางมากที่สุด นั่นเป็นเพราะ คุณสามารถกำหนดขอบเขต ViewModel ไปยังปลายทางการนำทาง กราฟการนำทาง กิจกรรมและส่วนย่อย

ดูข้อมูลเพิ่มเติมได้ในคู่มือเกี่ยวกับการยกรัฐสำหรับ Jetpack Compose

ใช้ ViewModel

ต่อไปนี้เป็นตัวอย่างการใช้งาน ViewModel สำหรับหน้าจอที่ ซึ่งช่วยให้ผู้ใช้ทอยลูกเต๋าได้

Kotlin

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,
            )
        }
    }
}

Java

public class DiceUiState {
    private final Integer firstDieValue;
    private final Integer secondDieValue;
    private final int numberOfRolls;

    // ...
}

public class DiceRollViewModel extends ViewModel {

    private final MutableLiveData<DiceUiState> uiState =
        new MutableLiveData(new DiceUiState(null, null, 0));
    public LiveData<DiceUiState> getUiState() {
        return uiState;
    }

    public void rollDice() {
        Random random = new Random();
        uiState.setValue(
            new DiceUiState(
                random.nextInt(7) + 1,
                random.nextInt(7) + 1,
                uiState.getValue().getNumberOfRolls() + 1
            )
        );
    }
}

จากนั้นคุณจะเข้าถึง ViewModel จากกิจกรรมได้โดยทำดังนี้

Kotlin

import androidx.activity.viewModels

class DiceRollActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        // Create a ViewModel the first time the system calls an activity's onCreate() method.
        // Re-created activities receive the same DiceRollViewModel instance created by the first activity.

        // Use the 'by viewModels()' Kotlin property delegate
        // from the activity-ktx artifact
        val viewModel: DiceRollViewModel by viewModels()
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect {
                    // Update UI elements
                }
            }
        }
    }
}

Java

public class MyActivity extends AppCompatActivity {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Create a ViewModel the first time the system calls an activity's onCreate() method.
        // Re-created activities receive the same MyViewModel instance created by the first activity.
        DiceRollViewModel model = new ViewModelProvider(this).get(DiceRollViewModel.class);
        model.getUiState().observe(this, uiState -> {
            // update UI
        });
    }
}

Jetpack Compose

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 กับสถาปัตยกรรม Android คอมโพเนนต์

วงจรชีวิตของ ViewModel

วงจรของ ViewModel เชื่อมโยงกับขอบเขตโดยตรง ViewModel จะยังอยู่ในหน่วยความจำจนถึง ViewModelStoreOwner ซึ่งมีการกำหนดขอบเขต หายไป ซึ่งอาจเกิดขึ้นในบริบทต่อไปนี้

  • ในกรณีที่มีกิจกรรม ให้ถือว่าสิ้นสุด
  • ในกรณีที่มีเศษส่วนอยู่ ส่วนที่แยกออกมา
  • ในกรณีของรายการการนำทาง เมื่อนำรายการดังกล่าวออกจากสแต็กด้านหลัง

สิ่งนี้ทำให้ ViewModels เป็นโซลูชันที่ยอดเยี่ยมสำหรับการจัดเก็บข้อมูลที่ใช้ได้จริง การเปลี่ยนแปลงการกำหนดค่า

รูปที่ 1 แสดงถึงสถานะในวงจรต่างๆ ของกิจกรรมที่เกิดขึ้น หมุนเวียนไปเรื่อยๆ จนครบ ภาพประกอบแสดงอายุการใช้งาน ViewModel ถัดจากวงจรกิจกรรมที่เกี่ยวข้อง ส่วนนี้ แผนภาพแสดงสถานะของกิจกรรม สถานะพื้นฐานเหมือนกันจะมีผลกับ วงจรของส่วนย่อย

แสดงวงจรชีวิตของ ViewModel เมื่อกิจกรรมเปลี่ยนแปลงสถานะ

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

กำลังล้างทรัพยากร Dependency ของ ViewModel

ViewModel เรียกเมธอด onCleared เมื่อ ViewModelStoreOwner จะทำลายผลิตภัณฑ์นั้นไปตลอดวงจรชีวิต ซึ่งจะช่วยให้คุณล้างข้อมูลงานต่างๆ หรือทรัพยากร Dependency ที่อยู่หลังวงจรของ 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()
    }
}

ตั้งแต่อายุการใช้งาน เวอร์ชัน 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:

  • เนื่องจากการกำหนดขอบเขต ให้ใช้ ViewModels เป็นรายละเอียดการใช้งาน ตัวยึดสถานะระดับหน้าจอ อย่าใช้ไอคอนเหล่านี้เป็นเจ้าของสถานะของ UI ที่นำมาใช้ใหม่ได้ คอมโพเนนต์ เช่น กลุ่มชิปหรือรูปแบบ มิฉะนั้นคุณจะได้รับ อินสแตนซ์ ViewModel ในการใช้งานคอมโพเนนต์ UI เดียวกันในลักษณะที่แตกต่างกันภายใต้ ViewModelStoreOwner เว้นแต่คุณใช้คีย์โมเดลการดูที่ชัดแจ้งต่อชิป
  • ViewModels ไม่ควรทราบเกี่ยวกับรายละเอียดการใช้งาน UI เก็บชื่อไว้ ของเมธอดที่ ViewModel API แสดงและช่องสถานะ UI กว้างที่สุดเท่าที่จะทำได้ ด้วยวิธีนี้ ViewModel ของคุณสามารถรองรับ UI: โทรศัพท์มือถือ อุปกรณ์แบบพับได้ แท็บเล็ต หรือแม้แต่ Chromebook
  • เนื่องจากอาจมีอายุนานกว่า ViewModelStoreOwner, ViewModels ไม่ควรเก็บข้อมูลอ้างอิงของ API ที่เกี่ยวข้องกับวงจร เช่น Context หรือ Resources เพื่อป้องกันการรั่วไหลของหน่วยความจำ
  • อย่าส่ง ViewModels ไปยังคลาส ฟังก์ชัน หรือคอมโพเนนต์ UI อื่นๆ และเนื่องจากแพลตฟอร์มจะจัดการสิ่งเหล่านี้ คุณควรทำให้พาร์ทเนอร์เหล่านี้ใกล้ชิดกับคุณมากที่สุด ได้ ใกล้กับฟังก์ชันที่ประกอบกันได้ ได้แก่ กิจกรรม ส่วนย่อย หรือหน้าจอ ซึ่งจะป้องกันไม่ให้คอมโพเนนต์ระดับล่างเข้าถึงข้อมูลและตรรกะมากกว่า ที่จำเป็น

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

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

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

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

ดูข้อมูลเพิ่มเติมเกี่ยวกับชั้นเรียน ViewModel ได้ที่ ที่ไม่ซับซ้อน

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

ตัวอย่าง