UI ที่ทันสมัยมักจะไม่คงที่ สถานะของ UI จะเปลี่ยนแปลงเมื่อผู้ใช้โต้ตอบกับ UI หรือเมื่อแอปต้องแสดงข้อมูลใหม่
เอกสารนี้กำหนดแนวทางสำหรับการสร้างและการจัดการสถานะ UI โดยมีวัตถุประสงค์เพื่อช่วยให้คุณเข้าใจสิ่งต่อไปนี้
- API ที่ใช้ในการสร้างสถานะ UI ซึ่งขึ้นอยู่กับลักษณะของ แหล่งที่มาของการเปลี่ยนแปลงสถานะที่มีอยู่ในตัวเก็บสถานะ โดยเป็นไปตาม หลักการของโฟลว์ข้อมูลแบบทิศทางเดียว
- วิธีจำกัดขอบเขตการสร้างสถานะ UI เพื่อให้ตระหนักถึงทรัพยากรของระบบ
- วิธีเปิดเผยสถานะ UI เพื่อให้ UI ใช้งานได้
โดยพื้นฐานแล้ว การสร้างสถานะคือการใช้การเปลี่ยนแปลงเหล่านี้กับสถานะ UI แบบค่อยเป็นค่อยไป สถานะมีอยู่เสมอและเปลี่ยนแปลงไปตามเหตุการณ์ต่างๆ ตารางด้านล่างสรุปความแตกต่างระหว่างเหตุการณ์และสถานะ
| เหตุการณ์ | สถานะ |
|---|---|
| ชั่วคราว คาดเดาไม่ได้ และมีอยู่เป็นระยะเวลาที่จำกัด | มีอยู่เสมอ |
| อินพุตของการสร้างสถานะ | เอาต์พุตของการสร้างสถานะ |
| ผลลัพธ์ของ UI หรือแหล่งที่มาอื่นๆ | UI ใช้งาน |
คำช่วยจำที่ดีที่สรุปข้างต้นคือ สถานะมีอยู่ เหตุการณ์เกิดขึ้น แผนภาพด้านล่างช่วยให้เห็นภาพการเปลี่ยนแปลงสถานะเมื่อเหตุการณ์เกิดขึ้นในไทม์ไลน์ ตัวเก็บสถานะที่เหมาะสมจะประมวลผลเหตุการณ์แต่ละรายการและส่งผลให้เกิดการ เปลี่ยนแปลงสถานะ
เหตุการณ์อาจมาจากแหล่งที่มาต่อไปนี้
- ผู้ใช้: เมื่อโต้ตอบกับ UI ของแอป
- แหล่งที่มาอื่นๆ ของการเปลี่ยนแปลงสถานะ: API ที่แสดงข้อมูลแอปจากเลเยอร์ UI, โดเมน หรือข้อมูล เช่น เหตุการณ์หมดเวลาของ Snackbar, Use Case หรือ Repository ตามลำดับ
ไปป์ไลน์การสร้างสถานะ UI
การสร้างสถานะในแอป Android สามารถมองได้ว่าเป็นไปป์ไลน์การประมวลผลที่ประกอบด้วยสิ่งต่อไปนี้
- อินพุต: แหล่งที่มาของการเปลี่ยนแปลงสถานะ ซึ่งอาจเป็นสิ่งต่อไปนี้
- เฉพาะเลเยอร์ UI: อาจเป็นเหตุการณ์ของผู้ใช้ เช่น ผู้ใช้ป้อน
ชื่อสำหรับ "สิ่งที่ต้องทำ" ในแอปการจัดการงาน หรือ API ที่ให้
สิทธิ์เข้าถึง ตรรกะ UI ที่ขับเคลื่อนการเปลี่ยนแปลงสถานะ UI เช่น
การเรียกใช้เมธอด
openในDrawerStateใน Jetpack Compose - ภายนอกเลเยอร์ UI: แหล่งที่มาจากเลเยอร์โดเมนหรือข้อมูลที่ทำให้เกิดการเปลี่ยนแปลงสถานะ UI เช่น ข่าวที่โหลดเสร็จจาก
NewsRepositoryหรือเหตุการณ์อื่นๆ - การผสมผสานระหว่างแหล่งที่มาข้างต้น
- เฉพาะเลเยอร์ UI: อาจเป็นเหตุการณ์ของผู้ใช้ เช่น ผู้ใช้ป้อน
ชื่อสำหรับ "สิ่งที่ต้องทำ" ในแอปการจัดการงาน หรือ API ที่ให้
สิทธิ์เข้าถึง ตรรกะ UI ที่ขับเคลื่อนการเปลี่ยนแปลงสถานะ UI เช่น
การเรียกใช้เมธอด
- ตัวเก็บสถานะ: ประเภทที่ใช้ตรรกะทางธุรกิจและตรรกะ UIกับแหล่งที่มาของการเปลี่ยนแปลงสถานะ และประมวลผลเหตุการณ์ของผู้ใช้เพื่อสร้าง สถานะ UI
- เอาต์พุต: สถานะ UI ที่แอปแสดงผลได้เพื่อให้ข้อมูลที่ผู้ใช้ต้องการ
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 ได้จากแหล่งข้อมูลเพิ่มเติมต่อไปนี้
เอกสารประกอบ
ดูเนื้อหา
แนะนำสำหรับคุณ
- หมายเหตุ: ข้อความลิงก์จะแสดงเมื่อ JavaScript ปิดอยู่
- เลเยอร์ UI
- สร้างแอปที่ทำงานแบบออฟไลน์เป็นหลัก
- ตัวเก็บสถานะและสถานะ UI {:#mad-arch}