การสร้างสถานะ UI

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

เอกสารนี้ระบุหลักเกณฑ์ในการสร้างและการจัดการ UI สุดท้าย คุณควรทำดังนี้

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

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

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

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

วันที่ เหตุการณ์กับสถานะ
รูปที่ 1: เหตุการณ์ทำให้สถานะเปลี่ยนแปลง

กิจกรรมอาจมาจาก

  • ผู้ใช้: ในขณะที่โต้ตอบกับ UI ของแอป
  • แหล่งที่มาอื่นๆ ของการเปลี่ยนแปลงสถานะ: API ที่นำเสนอข้อมูลแอปจาก UI โดเมน หรือชั้นข้อมูล เช่น เหตุการณ์ระยะหมดเวลาของแถบแสดงข้อความ กรณีการใช้งาน หรือ ที่เก็บตามลำดับ

ไปป์ไลน์การสร้างสถานะ UI

เวอร์ชันที่ใช้งานจริงในแอป Android ถือว่าเป็นไปป์ไลน์การประมวลผล ซึ่งประกอบด้วย:

  • อินพุต: แหล่งที่มาของการเปลี่ยนแปลงสถานะ ซึ่งอาจเป็น
    • ภายในเลเยอร์ UI: เหตุการณ์เหล่านี้อาจเป็นเหตุการณ์ของผู้ใช้ เช่น ผู้ใช้ป้อน ชื่อสำหรับ "สิ่งที่ต้องทำ" ในแอปการจัดการงาน หรือ API ที่ สิทธิ์เข้าถึงตรรกะ UI ที่กระตุ้นให้เกิดการเปลี่ยนแปลงสถานะ UI ตัวอย่างเช่น การเรียกใช้เมธอด open ใน DrawerState ใน Jetpack Compose
    • ภายนอกเลเยอร์ UI: ข้อมูลเหล่านี้คือแหล่งที่มาจากโดเมนหรือข้อมูล ที่ทำให้สถานะของ UI เปลี่ยนไป เช่น ข่าวที่จบแล้ว ที่โหลดจาก NewsRepository หรือเหตุการณ์อื่นๆ
    • ทั้งหมดที่กล่าวมาข้างต้น
  • ผู้ถือรัฐ: ประเภทที่ใช้ตรรกะทางธุรกิจและ/หรือ ตรรกะ UI ไปยังแหล่งที่มาของการเปลี่ยนแปลงสถานะและประมวลผลเหตุการณ์ของผู้ใช้เพื่อสร้าง สถานะ UI
  • เอาต์พุต: สถานะ UI ที่แอปแสดงผลได้เพื่อให้ผู้ใช้ ข้อมูลที่ต้องการได้อีกด้วย
ไปป์ไลน์การผลิตสถานะ
รูปที่ 2: ไปป์ไลน์การผลิตสถานะ

API การผลิตสถานะ

API หลักที่ใช้ในเวอร์ชันที่ใช้งานจริงจะมี 2 แบบ ขึ้นอยู่กับขั้นตอนของ ไปป์ไลน์ที่คุณใช้อยู่:

ขั้นตอนไปป์ไลน์ API
อินพุต คุณควรใช้ API แบบอะซิงโครนัสในการทำงานนอกเธรด UI เพื่อทำให้ UI มีความกระตุกอยู่เสมอ เช่น Coroutines หรือ Flows ใน Kotlin และ RxJava หรือ Callback ในภาษาโปรแกรม Java
เอาต์พุต คุณควรใช้ API ผู้ถือข้อมูลที่ได้รับอนุญาตให้สังเกตพฤติกรรมผู้ใช้ได้เพื่อทำให้ UI เป็นโมฆะและแสดง UI อีกครั้งเมื่อมีการเปลี่ยนแปลงสถานะ ตัวอย่างเช่น StateFlow, Compose State หรือ LiveData ผู้ถือข้อมูลที่สังเกตได้จะรับประกันว่า UI จะมีสถานะ UI ที่จะแสดงบนหน้าจอเสมอ

จากตัวเลือกทั้ง 2 อย่าง ตัวเลือก API แบบอะซิงโครนัสสำหรับอินพุตมีอิทธิพลมากกว่า ลักษณะของไปป์ไลน์การผลิตของรัฐมากกว่าตัวเลือก API ที่สังเกตได้ สำหรับเอาต์พุต นั่นเป็นเพราะอินพุตเป็นตัวกำหนดประเภทการประมวลผลข้อมูลที่อาจ กับไปป์ไลน์

การประกอบกระบวนการผลิตของรัฐ

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

  • การทราบอายุการใช้งาน: ในกรณีที่ UI ไม่ปรากฏหรือไม่ทํางานจริง ไปป์ไลน์การผลิตสถานะไม่ควรใช้ทรัพยากรใดๆ ยกเว้นอย่างชัดแจ้ง ต้องระบุ
  • ใช้งานง่าย: UI ควรสามารถแสดงผล UI ที่สร้างขึ้นได้อย่างง่ายดาย ข้อควรพิจารณาสำหรับเอาต์พุตของไปป์ไลน์เวอร์ชันที่ใช้งานจริงของรัฐจะ แตกต่างกันไปตาม View API ต่างๆ เช่น ระบบ View หรือ Jetpack Compose

อินพุตในไปป์ไลน์การผลิตสถานะ

อินพุตในไปป์ไลน์การผลิตสถานะอาจระบุแหล่งที่มาของสถานะ เปลี่ยนผ่าน:

  • การดำเนินการแบบจุดเดียวซึ่งอาจเป็นการทำงานแบบซิงโครนัสหรืออะซิงโครนัส เช่น การเรียกไปยังฟังก์ชัน suspend
  • API ของสตรีม เช่น Flows
  • ทั้งหมดที่กล่าวมา

ส่วนต่อไปนี้ครอบคลุมวิธีประกอบไปป์ไลน์การผลิตสถานะ สำหรับข้อมูลข้างต้นแต่ละรายการ

API แบบ One-shot เป็นแหล่งที่มาของการเปลี่ยนแปลงสถานะ

ใช้ MutableStateFlow API เป็นแบบที่สังเกตได้และเปลี่ยนแปลงได้ คอนเทนเนอร์ของสถานะ ในแอป Jetpack Compose คุณอาจลองใช้ mutableStateOf โดยเฉพาะเมื่อทำงานร่วมกับ Compose Text API API ทั้ง 2 แบบเสนอวิธีที่ปลอดภัย อัปเดตระดับย่อยของค่าที่โฮสต์ไว้ ไม่ว่าการอัปเดตนั้น แบบซิงโครนัสหรืออะซิงโครนัส

เช่น พิจารณาการอัปเดตสถานะในแอปทอยลูกเต๋าแบบง่ายๆ แต่ละม้วน ลูกเต๋าที่ได้จากผู้ใช้จะเรียกการทำงานพร้อมกัน Random.nextInt() และผลลัพธ์จะถูกเขียนลงใน สถานะ UI

โฟลว์สถานะ

data class DiceUiState(
    val firstDieValue: Int? = null,
    val secondDieValue: Int? = null,
    val numberOfRolls: Int = 0,
)

class DiceRollViewModel : ViewModel() {

    private val _uiState = MutableStateFlow(DiceUiState())
    val uiState: StateFlow<DiceUiState> = _uiState.asStateFlow()

    // Called from the UI
    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,
            )
        }
    }
}

สถานะการเขียน

@Stable
interface DiceUiState {
    val firstDieValue: Int?
    val secondDieValue: Int?
    val numberOfRolls: Int?
}

private class MutableDiceUiState: DiceUiState {
    override var firstDieValue: Int? by mutableStateOf(null)
    override var secondDieValue: Int? by mutableStateOf(null)
    override var numberOfRolls: Int by mutableStateOf(0)
}

class DiceRollViewModel : ViewModel() {

    private val _uiState = MutableDiceUiState()
    val uiState: DiceUiState = _uiState

    // Called from the UI
    fun rollDice() {
        _uiState.firstDieValue = Random.nextInt(from = 1, until = 7)
        _uiState.secondDieValue = Random.nextInt(from = 1, until = 7)
        _uiState.numberOfRolls = _uiState.numberOfRolls + 1
    }
}

การเปลี่ยนแปลงสถานะ UI จากการเรียกแบบไม่พร้อมกัน

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

ตัวอย่างเช่น ลองพิจารณา AddEditTaskViewModel ใน ตัวอย่างสถาปัตยกรรม เมื่อเมธอด saveTask() ที่ถูกระงับ จะบันทึกงานแบบไม่พร้อมกัน เมธอด update ใน MutableStateFlow เผยแพร่การเปลี่ยนแปลงสถานะเป็นสถานะ UI

โฟลว์สถานะ

data class AddEditTaskUiState(
    val title: String = "",
    val description: String = "",
    val isTaskCompleted: Boolean = false,
    val isLoading: Boolean = false,
    val userMessage: String? = null,
    val isTaskSaved: Boolean = false
)

class AddEditTaskViewModel(...) : ViewModel() {

   private val _uiState = MutableStateFlow(AddEditTaskUiState())
   val uiState: StateFlow<AddEditTaskUiState> = _uiState.asStateFlow()

   private fun createNewTask() {
        viewModelScope.launch {
            val newTask = Task(uiState.value.title, uiState.value.description)
            try {
                tasksRepository.saveTask(newTask)
                // Write data into the UI state.
                _uiState.update {
                    it.copy(isTaskSaved = true)
                }
            }
            catch(cancellationException: CancellationException) {
                throw cancellationException
            }
            catch(exception: Exception) {
                _uiState.update {
                    it.copy(userMessage = getErrorMessage(exception))
                }
            }
        }
    }
}

สถานะการเขียน

@Stable
interface AddEditTaskUiState {
    val title: String
    val description: String
    val isTaskCompleted: Boolean
    val isLoading: Boolean
    val userMessage: String?
    val isTaskSaved: Boolean
}

private class MutableAddEditTaskUiState : AddEditTaskUiState() {
    override var title: String by mutableStateOf("")
    override var description: String by mutableStateOf("")
    override var isTaskCompleted: Boolean by mutableStateOf(false)
    override var isLoading: Boolean by mutableStateOf(false)
    override var userMessage: String? by mutableStateOf<String?>(null)
    override var isTaskSaved: Boolean by mutableStateOf(false)
}

class AddEditTaskViewModel(...) : ViewModel() {

   private val _uiState = MutableAddEditTaskUiState()
   val uiState: AddEditTaskUiState = _uiState

   private fun createNewTask() {
        viewModelScope.launch {
            val newTask = Task(uiState.value.title, uiState.value.description)
            try {
                tasksRepository.saveTask(newTask)
                // Write data into the UI state.
                _uiState.isTaskSaved = true
            }
            catch(cancellationException: CancellationException) {
                throw cancellationException
            }
            catch(exception: Exception) {
                _uiState.userMessage = getErrorMessage(exception))
            }
        }
    }
}

การเปลี่ยนสถานะ UI จากชุดข้อความเบื้องหลัง

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

  • ใช้เมธอด withContext เพื่อเรียกใช้ Coroutines ใน ในบริบทที่แตกต่างกัน
  • เมื่อใช้ MutableStateFlow ให้ใช้เมธอด update เป็น ตามปกติ
  • เมื่อใช้สถานะการเขียน ให้ใช้ Snapshot.withMutableSnapshot เพื่อ รับประกันการอัปเดตระดับอะตอมของสถานะในบริบทที่เกิดขึ้นพร้อมกัน

ตัวอย่างเช่น สมมติว่าในข้อมูลโค้ด DiceRollViewModel ด้านล่างนี้ SlowRandom.nextInt() เป็น suspend ที่มีการคำนวณอย่างหนัก ที่ต้องเรียกใช้จาก Coroutine ที่ผูกกับ CPU

โฟลว์สถานะ

class DiceRollViewModel(
    private val defaultDispatcher: CoroutineScope = Dispatchers.Default
) : ViewModel() {

    private val _uiState = MutableStateFlow(DiceUiState())
    val uiState: StateFlow<DiceUiState> = _uiState.asStateFlow()

  // Called from the UI
  fun rollDice() {
        viewModelScope.launch() {
            // Other Coroutines that may be called from the current context
            
            withContext(defaultDispatcher) {
                _uiState.update { currentState ->
                    currentState.copy(
                        firstDieValue = SlowRandom.nextInt(from = 1, until = 7),
                        secondDieValue = SlowRandom.nextInt(from = 1, until = 7),
                        numberOfRolls = currentState.numberOfRolls + 1,
                    )
                }
            }
        }
    }
}

สถานะการเขียน

class DiceRollViewModel(
    private val defaultDispatcher: CoroutineScope = Dispatchers.Default
) : ViewModel() {

    private val _uiState = MutableDiceUiState()
    val uiState: DiceUiState = _uiState

  // Called from the UI
  fun rollDice() {
        viewModelScope.launch() {
            // Other Coroutines that may be called from the current context
            
            withContext(defaultDispatcher) {
                Snapshot.withMutableSnapshot {
                    _uiState.firstDieValue = SlowRandom.nextInt(from = 1, until = 7)
                    _uiState.secondDieValue = SlowRandom.nextInt(from = 1, until = 7)
                    _uiState.numberOfRolls = _uiState.numberOfRolls + 1
                }
            }
        }
    }
}

สตรีม API เป็นแหล่งที่มาของการเปลี่ยนแปลงสถานะ

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

เมื่อใช้ Kotlin Flows คุณสามารถทําได้ด้วยชุดค่าผสม ตัวอย่างของการเปลี่ยนแปลงนี้สามารถดูได้ใน "ตอนนี้ใน Android" ตัวอย่างใน InterestViewModel:

class InterestsViewModel(
    authorsRepository: AuthorsRepository,
    topicsRepository: TopicsRepository
) : ViewModel() {

    val uiState = combine(
        authorsRepository.getAuthorsStream(),
        topicsRepository.getTopicsStream(),
    ) { availableAuthors, availableTopics ->
        InterestsUiState.Interests(
            authors = availableAuthors,
            topics = availableTopics
        )
    }
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5_000),
            initialValue = InterestsUiState.Loading
    )
}

การใช้โอเปอเรเตอร์ stateIn เพื่อสร้าง StateFlows ช่วยให้ UI ละเอียดยิ่งขึ้น ควบคุมกิจกรรมของไปป์ไลน์การผลิตของรัฐเนื่องจากอาจต้อง ทำงานเฉพาะเมื่อเห็น UI

  • ใช้ SharingStarted.WhileSubscribed() หากไปป์ไลน์ควรทำงานอยู่เท่านั้น เมื่อมองเห็น UI ขณะรวบรวมโฟลว์แบบรับรู้วงจร ลักษณะ
  • ใช้ SharingStarted.Lazily หากไปป์ไลน์ควรทำงานอยู่ตราบเท่าที่แอตทริบิวต์ ผู้ใช้อาจกลับไปยัง UI นั่นคือ UI อยู่ด้านหลัง หรือในอีก แท็บออกนอกหน้าจอ

ในกรณีที่ไม่มีการรวมแหล่งที่มาของรัฐที่อิงตามสตรีม ให้สตรีม API อย่าง Kotlin Flows มีการเปลี่ยนรูปแบบมากมาย เช่น การผสาน flattening และอื่นๆ เป็น ช่วยประมวลผลสตรีมให้อยู่ในสถานะ UI

ใช้ One-shot และ Stream API เป็นแหล่งที่มาของการเปลี่ยนแปลงสถานะ

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

เมื่อมีขั้นตอน การดำเนินการดังกล่าวมักจะหมายถึงการสร้างการสำรองข้อมูลส่วนตัวอย่างน้อย 1 รายการ MutableStateFlow อินสแตนซ์เพื่อเปลี่ยนแปลงสถานะการเผยแพร่ นอกจากนี้คุณยัง สร้างโฟลว์สแนปชอตจากสถานะการเขียน

โปรดพิจารณา TaskDetailViewModel จาก Architecture-ตัวอย่าง ด้านล่าง

โฟลว์สถานะ

class TaskDetailViewModel @Inject constructor(
    private val tasksRepository: TasksRepository,
    savedStateHandle: SavedStateHandle
) : ViewModel() {

    private val _isTaskDeleted = MutableStateFlow(false)
    private val _task = tasksRepository.getTaskStream(taskId)

    val uiState: StateFlow<TaskDetailUiState> = combine(
        _isTaskDeleted,
        _task
    ) { isTaskDeleted, task ->
        TaskDetailUiState(
            task = taskAsync.data,
            isTaskDeleted = isTaskDeleted
        )
    }
        // Convert the result to the appropriate observable API for the UI
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5_000),
            initialValue = TaskDetailUiState()
        )

    fun deleteTask() = viewModelScope.launch {
        tasksRepository.deleteTask(taskId)
        _isTaskDeleted.update { true }
    }
}

สถานะการเขียน

class TaskDetailViewModel @Inject constructor(
    private val tasksRepository: TasksRepository,
    savedStateHandle: SavedStateHandle
) : ViewModel() {

    private var _isTaskDeleted by mutableStateOf(false)
    private val _task = tasksRepository.getTaskStream(taskId)

    val uiState: StateFlow<TaskDetailUiState> = combine(
        snapshotFlow { _isTaskDeleted },
        _task
    ) { isTaskDeleted, task ->
        TaskDetailUiState(
            task = taskAsync.data,
            isTaskDeleted = isTaskDeleted
        )
    }
        // Convert the result to the appropriate observable API for the UI
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5_000),
            initialValue = TaskDetailUiState()
        )

    fun deleteTask() = viewModelScope.launch {
        tasksRepository.deleteTask(taskId)
        _isTaskDeleted = true
    }
}

ประเภทเอาต์พุตในไปป์ไลน์การผลิตสถานะ

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

ตารางต่อไปนี้สรุป API ที่จะใช้สําหรับเวอร์ชันที่ใช้งานจริงของรัฐ ไปป์ไลน์สําหรับอินพุตและผู้บริโภคที่ระบุ

อินพุต ผู้บริโภค เอาต์พุต
API แบบ One-shot ยอดดู StateFlow หรือ LiveData
API แบบ One-shot เขียน StateFlow หรือเขียน State
API ของสตรีม ยอดดู StateFlow หรือ LiveData
API ของสตรีม เขียน StateFlow
API แบบ One-shot and Stream API ยอดดู StateFlow หรือ LiveData
API แบบ One-shot and Stream API เขียน StateFlow

การเริ่มต้นไปป์ไลน์การผลิตสถานะ

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

คุณควรเริ่มต้นไปป์ไลน์การผลิตสถานะแบบ Lazy Loading หากเป็นไปได้ เพื่อประหยัดทรัพยากรของระบบ ซึ่งในทางปฏิบัติแล้ว มักจะหมายถึงการรอจนกว่าจะมีผู้บริโภคผลิตภัณฑ์ เอาต์พุต Flow API อนุญาตการดำเนินการนี้ด้วย อาร์กิวเมนต์ started ใน stateIn ในกรณีที่ไม่เกี่ยวข้อง กำหนด idempotent initialize() เพื่อเริ่มไปป์ไลน์การผลิตสถานะอย่างชัดแจ้ง ดังที่แสดงในข้อมูลโค้ดต่อไปนี้

class MyViewModel : ViewModel() {

    private var initializeCalled = false

    // This function is idempotent provided it is only called from the UI thread.
    @MainThread
    fun initialize() {
        if(initializeCalled) return
        initializeCalled = true

        viewModelScope.launch {
            // seed the state production pipeline
        }
    }
}

ตัวอย่าง

ตัวอย่าง Google ต่อไปนี้สาธิตการสร้างรัฐใน เลเยอร์ UI ศึกษาคู่มือเพื่อดูคำแนะนำนี้ในสถานการณ์จริง