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

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

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

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

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

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

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

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

เหตุการณ์อาจมาจากแหล่งที่มาต่อไปนี้

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

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

การสร้างสถานะในแอป Android สามารถมองได้ว่าเป็นไปป์ไลน์การประมวลผลที่ประกอบด้วยสิ่งต่อไปนี้

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

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

มี API หลัก 2 รายการที่ใช้ในการสร้างสถานะ ซึ่งขึ้นอยู่กับขั้นตอนของไปป์ไลน์ที่คุณอยู่

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

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

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

ส่วนถัดไปจะครอบคลุมเทคนิคการสร้างสถานะที่เหมาะกับอินพุตต่างๆ และ API เอาต์พุตที่ตรงกัน ไปป์ไลน์การสร้างสถานะแต่ละรายการเป็นการผสมผสานระหว่างอินพุตและเอาต์พุต และต้องมีลักษณะดังต่อไปนี้

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

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

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

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

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

API แบบครั้งเดียวเป็นแหล่งที่มาของการเปลี่ยนแปลงสถานะ

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

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

Compose State

@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
    }
}

StateFlow

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

การเปลี่ยนแปลงสถานะ UI จากการเรียกใช้แบบอะซิงโครนัส

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

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

Compose State

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

StateFlow

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

การเปลี่ยนแปลงสถานะ UI จากเธรดเบื้องหลัง

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

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

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

Compose State

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

StateFlow

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

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

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

เมื่อใช้ Kotlin Flows คุณสามารถทำได้ด้วยฟังก์ชัน combine ตัวอย่างของฟังก์ชันนี้ดูได้ในตัวอย่าง "Now in Android" ใน InterestsViewModel

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 อยู่ใน Backstack หรือในแท็บอื่นนอกหน้าจอ

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

API แบบครั้งเดียวและ API สตรีมเป็นแหล่งที่มาของการเปลี่ยนแปลงสถานะ

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

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

ลองพิจารณา TaskDetailViewModel จากที่เก็บ architecture-samples สถานะ UI ขึ้นอยู่กับสตรีมสำหรับงานปัจจุบัน (_task) และแหล่งที่มาแบบครั้งเดียว (_isTaskDeleted) ที่อัปเดตเมื่อมีการลบงาน แฟล็กนี้จำเป็นเพื่อแยกความแตกต่างระหว่างกรณีที่ระบบไม่พบงานในฐานข้อมูลเนื่องจาก ID ไม่ถูกต้อง กับกรณีที่ระบบไม่พบงานเนื่องจากผู้ใช้เพิ่งลบงาน

Compose State

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

StateFlow

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

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

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

ตารางต่อไปนี้สรุป API ที่ควรใช้สำหรับไปป์ไลน์การสร้างสถานะเมื่อใช้ Jetpack Compose

อินพุต เอาต์พุต
API แบบครั้งเดียว StateFlow หรือ State ของ Compose
API สตรีม StateFlow
API แบบครั้งเดียวและ API สตรีม StateFlow

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

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

เริ่มต้นไปป์ไลน์การสร้างสถานะแบบเลื่อนออกไปเมื่อเป็นไปได้เพื่อประหยัดทรัพยากรของระบบ ในทางปฏิบัติแล้ว มักหมายถึงการรอจนกว่าจะมีผู้ใช้เอาต์พุต API Flow อนุญาตให้ดำเนินการนี้ได้ด้วยอาร์กิวเมนต์ started ในเมธอด stateIn ในกรณีที่ไม่สามารถใช้ได้ ให้กำหนด ฟังก์ชัน ไอเดมโพเทนต์ 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 ลองสำรวจตัวอย่างเหล่านี้เพื่อดูคำแนะนำนี้ในทางปฏิบัติ

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

ดูข้อมูลเพิ่มเติมเกี่ยวกับสถานะ UI ได้จากแหล่งข้อมูลเพิ่มเติมต่อไปนี้

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

ดูเนื้อหา