ในแอปพลิเคชัน Compose ตำแหน่งที่คุณยก สถานะ UI ขึ้นอยู่กับว่าตรรกะ UI หรือตรรกะทางธุรกิจกำหนดให้ทำเช่นนั้นหรือไม่ เอกสารนี้จะอธิบายสถานการณ์หลัก 2 สถานการณ์
แนวทางปฏิบัติแนะนำ
คุณควรยกสถานะ UI ขึ้นไปยังบรรพบุรุษร่วมที่ต่ำที่สุด ระหว่าง Composable ทั้งหมดที่อ่านและเขียนสถานะ และควรเก็บสถานะไว้ใกล้กับตำแหน่งที่ใช้สถานะนั้นมากที่สุด โดยเจ้าของสถานะควรแสดงสถานะที่ไม่เปลี่ยนแปลงและเหตุการณ์ต่างๆ ให้ผู้ใช้เพื่อแก้ไขสถานะ
บรรพบุรุษร่วมที่ต่ำที่สุดอาจอยู่นอก Composition ก็ได้ เช่น เมื่อยกสถานะขึ้นใน ViewModel เนื่องจากมีตรรกะทางธุรกิจเข้ามาเกี่ยวข้อง
หน้านี้จะอธิบายแนวทางปฏิบัติแนะนำนี้โดยละเอียดและข้อควรระวังที่ควรทราบ
ประเภทของสถานะ UI และตรรกะ UI
ด้านล่างนี้คือคำจำกัดความของประเภทสถานะและตรรกะ UI ที่ใช้ในเอกสารนี้
สถานะ UI
สถานะ UI คือพร็อพเพอร์ตี้ที่ อธิบาย UI โดยมี 2 ประเภทดังนี้
- สถานะ UI ของหน้าจอ คือ สิ่งที่ คุณต้องแสดงบนหน้าจอ เช่น คลาส
NewsUiStateอาจมีบทความข่าวและข้อมูลอื่นๆ ที่จำเป็นต่อการแสดงผล UI สถานะนี้มักจะเชื่อมโยงกับเลเยอร์อื่นๆ ในลำดับชั้นเนื่องจากมีข้อมูลแอป - สถานะองค์ประกอบ UI หมายถึงพร็อพเพอร์ตี้ที่อยู่ในองค์ประกอบ UI ซึ่งส่งผลต่อวิธีแสดงผล องค์ประกอบ UI อาจแสดงหรือซ่อน และอาจมีแบบอักษร ขนาดแบบอักษร หรือสีแบบอักษรที่เฉพาะเจาะจง ใน Jetpack Compose สถานะจะอยู่นอก Composable และคุณยังสามารถยกสถานะออกจากบริเวณใกล้เคียงของ Composable ไปยังฟังก์ชัน Composable ที่เรียกใช้หรือตัวเก็บสถานะได้ด้วย ตัวอย่างของสถานะนี้คือ
ScaffoldStateสำหรับScaffoldComposable
ตรรกะ
ตรรกะในแอปพลิเคชันอาจเป็นตรรกะทางธุรกิจหรือตรรกะ UI ก็ได้
- ตรรกะทางธุรกิจ คือการนำข้อกำหนดของผลิตภัณฑ์สำหรับข้อมูลแอปไปใช้ เช่น การเพิ่มบทความลงในบุ๊กมาร์กในแอปอ่านข่าวเมื่อผู้ใช้แตะปุ่ม โดยปกติแล้วตรรกะในการบันทึกบุ๊กมาร์กลงในไฟล์หรือฐานข้อมูลจะอยู่ในเลเยอร์โดเมนหรือเลเยอร์ข้อมูล ตัวยึดสถานะมักจะมอบหมายตรรกะนี้ไปยังเลเยอร์เหล่านั้นโดยการเรียกใช้เมธอดที่เลเยอร์เหล่านั้นแสดง
- ตรรกะ UI เกี่ยวข้องกับ วิธี แสดงสถานะ UI บนหน้าจอ เช่น การรับคำแนะนำที่เหมาะสมสำหรับแถบค้นหาเมื่อผู้ใช้เลือกหมวดหมู่ การเลื่อนไปยังรายการที่เฉพาะเจาะจงในรายการ หรือตรรกะการนำทางไปยังหน้าจอที่เฉพาะเจาะจงเมื่อผู้ใช้คลิกปุ่ม
ตรรกะ UI
เมื่อ ตรรกะ UI ต้องอ่านหรือเขียนสถานะ คุณควรจำกัดขอบเขตสถานะไว้ที่ UI ตามวงจรการทำงานของ UI หากต้องการทำเช่นนี้ คุณควรยกสถานะขึ้นในระดับที่ถูกต้องในฟังก์ชันที่ประกอบกันได้ หรือจะทำใน คลาสตัวยึดสถานะธรรมดาที่จำกัดขอบเขตไว้ที่วงจรการทำงานของ UI ก็ได้
ด้านล่างนี้คือคำอธิบายของโซลูชันทั้ง 2 รายการและคำอธิบายเกี่ยวกับเวลาที่จะใช้โซลูชันใด
Composable เป็นเจ้าของสถานะ
การมีตรรกะ UI และสถานะองค์ประกอบ UI ใน Composable เป็นแนวทางที่ดีหากสถานะและตรรกะมีความซับซ้อนไม่มาก คุณสามารถเก็บสถานะไว้ภายใน Composable หรือยกสถานะขึ้นตามที่จำเป็น
ไม่จำเป็นต้องยกสถานะขึ้น
การยกสถานะขึ้นไม่จำเป็นเสมอไป คุณสามารถเก็บสถานะไว้ภายใน Composable ได้เมื่อไม่มี Composable อื่นที่ต้องควบคุมสถานะนั้น ในข้อมูลโค้ดนี้ มี Composable ที่ขยายและยุบเมื่อแตะ
@Composable fun ChatBubble( message: Message ) { var showDetails by rememberSaveable { mutableStateOf(false) } // Define the UI element expanded state Text( text = AnnotatedString(message.content), modifier = Modifier.clickable { showDetails = !showDetails // Apply UI logic } ) if (showDetails) { Text(message.timestamp) } }
ตัวแปร showDetails คือสถานะภายในขององค์ประกอบ UI นี้ โดยจะอ่านและแก้ไขใน Composable นี้เท่านั้น และตรรกะที่ใช้กับตัวแปรนี้ก็มีความซับซ้อนไม่มาก
ดังนั้นการยกสถานะขึ้นในกรณีนี้จึงไม่เป็นประโยชน์มากนัก คุณจึงเก็บสถานะไว้ภายในได้ การทำเช่นนี้จะทำให้ Composable นี้เป็นเจ้าของและแหล่งข้อมูลที่เชื่อถือได้แหล่งเดียวของสถานะที่ขยาย
การยกสถานะขึ้นภายใน Composable
หากต้องการแชร์สถานะองค์ประกอบ UI กับ Composable อื่นๆ และใช้ตรรกะ UI กับสถานะนั้นในที่ต่างๆ คุณสามารถยกสถานะขึ้นไปในระดับที่สูงขึ้นในลำดับชั้น UI ซึ่งยังทำให้ Composable ของคุณนำกลับมาใช้ซ้ำได้มากขึ้นและทดสอบได้ง่ายขึ้นด้วย
ตัวอย่างต่อไปนี้เป็นแอปแชทที่ใช้ฟังก์ชันการทำงาน 2 อย่าง
- ปุ่ม
JumpToBottomจะเลื่อนรายการข้อความไปที่ด้านล่าง ปุ่มนี้จะใช้ตรรกะ UI กับสถานะรายการ - รายการ
MessagesListจะเลื่อนไปที่ด้านล่างหลังจากที่ผู้ใช้ส่งข้อความใหม่ UserInput จะใช้ตรรกะ UI กับสถานะรายการ
JumpToBottom และเลื่อนไปที่ด้านล่างเมื่อมีข้อความใหม่ลำดับชั้น Composable มีดังนี้
สถานะ LazyColumn จะถูกยกขึ้นไปยังหน้าจอการสนทนาเพื่อให้แอปใช้
ตรรกะ UI และอ่านสถานะจาก Composable ทั้งหมดที่ต้องใช้สถานะนั้นได้
LazyColumn จาก LazyColumn ไปยัง ConversationScreenดังนั้น Composable สุดท้ายจึงมีลักษณะดังนี้
LazyListState ยกขึ้นไปยัง ConversationScreenโค้ดมีลักษณะดังนี้
@Composable private fun ConversationScreen(/*...*/) { val scope = rememberCoroutineScope() val lazyListState = rememberLazyListState() // State hoisted to the ConversationScreen MessagesList(messages, lazyListState) // Reuse same state in MessageList UserInput( onMessageSent = { // Apply UI logic to lazyListState scope.launch { lazyListState.scrollToItem(0) } }, ) } @Composable private fun MessagesList( messages: List<Message>, lazyListState: LazyListState = rememberLazyListState() // LazyListState has a default value ) { LazyColumn( state = lazyListState // Pass hoisted state to LazyColumn ) { items(messages, key = { message -> message.id }) { item -> Message(/*...*/) } } val scope = rememberCoroutineScope() JumpToBottom(onClicked = { scope.launch { lazyListState.scrollToItem(0) // UI logic being applied to lazyListState } }) }
LazyListState จะถูกยกขึ้นไปสูงเท่าที่จำเป็นสำหรับตรรกะ UI ที่ต้องใช้ เนื่องจากมีการเริ่มต้นในฟังก์ชันที่ประกอบกันได้ ระบบจึงจัดเก็บไว้ใน Composition ตามวงจรของ Composition
โปรดทราบว่า lazyListState กำหนดไว้ในเมธอด MessagesList โดยมีค่าเริ่มต้นเป็น rememberLazyListState() ซึ่งเป็นรูปแบบที่ใช้กันทั่วไปใน Compose
และทำให้ Composable นำกลับมาใช้ซ้ำได้มากขึ้นและมีความยืดหยุ่นมากขึ้น จากนั้นคุณจะใช้ Composable ในส่วนต่างๆ ของแอปที่ไม่จำเป็นต้องควบคุมสถานะได้ ซึ่งมักเกิดขึ้นขณะทดสอบหรือแสดงตัวอย่าง Composable และนี่คือวิธีที่ LazyColumn กำหนดสถานะของตัวเอง
LazyListState คือ ConversationScreenคลาสตัวยึดสถานะธรรมดาเป็นเจ้าของสถานะ
เมื่อ Composable มีตรรกะ UI ที่ซับซ้อนซึ่งเกี่ยวข้องกับฟิลด์สถานะ อย่างน้อย 1 รายการขององค์ประกอบ UI องค์ประกอบนั้นควรมอบหมายความรับผิดชอบดังกล่าวให้กับตัวยึดสถานะ เช่น คลาสตัวยึดสถานะธรรมดา ซึ่งจะทำให้ตรรกะของ Composable ทดสอบได้ง่ายขึ้นและลดความซับซ้อนลง แนวทางนี้สนับสนุน หลักการแยกความกังวลออกจากกัน: Composable มีหน้าที่ แสดงองค์ประกอบ UI และตัวยึดสถานะมีตรรกะ UI และสถานะ องค์ประกอบ UI
คลาสตัวเก็บสถานะธรรมดามีฟังก์ชันที่สะดวกสำหรับผู้เรียกใช้ฟังก์ชันที่ประกอบกันได้ เพื่อให้ผู้เรียกใช้ไม่ต้องเขียนตรรกะนี้เอง
ระบบจะสร้างและจดจำคลาสธรรมดาเหล่านี้ใน Composition เนื่องจากคลาสเหล่านี้
เป็นไปตามวงจรการทำงานของ Composable จึงใช้ประเภทที่ไลบรารี
Compose มีให้ได้ เช่น rememberNavController() หรือ rememberLazyListState()
ตัวอย่างของคลาสนี้คือ LazyListState ตัวยึดสถานะธรรมดา
คลาส ซึ่งใช้ใน Compose เพื่อควบคุมความซับซ้อน UI ของ LazyColumn
หรือ LazyRow
// LazyListState.kt @Stable class LazyListState constructor( firstVisibleItemIndex: Int = 0, firstVisibleItemScrollOffset: Int = 0 ) : ScrollableState { /** * The holder class for the current scroll position. */ private val scrollPosition = LazyListScrollPosition( firstVisibleItemIndex, firstVisibleItemScrollOffset ) suspend fun scrollToItem(/*...*/) { /*...*/ } override suspend fun scroll() { /*...*/ } suspend fun animateScrollToItem() { /*...*/ } }
LazyListState ห่อหุ้มสถานะของ LazyColumn โดยจัดเก็บ
scrollPosition สำหรับองค์ประกอบ UI นี้ นอกจากนี้ยังแสดงเมธอดเพื่อแก้ไขตำแหน่งการเลื่อน เช่น การเลื่อนไปยังรายการที่ระบุ
คุณจะเห็นว่าการเพิ่มความรับผิดชอบของ Composable จะเพิ่มความจำเป็นในการใช้ตัวเก็บสถานะ ความรับผิดชอบอาจอยู่ในตรรกะ UI หรือเพียงแค่จำนวนสถานะที่ต้องติดตาม
อีกรูปแบบที่ใช้กันทั่วไปคือการใช้คลาสตัวยึดสถานะธรรมดาเพื่อจัดการความซับซ้อนของฟังก์ชัน Composable ระดับรากในแอป คุณสามารถใช้คลาสดังกล่าวเพื่อห่อหุ้มสถานะระดับแอป เช่น สถานะการนำทางและการปรับขนาดหน้าจอ ดูคำอธิบายทั้งหมด ได้ในหน้าตรรกะ UI และตัวเก็บสถานะ
ตรรกะทางธุรกิจ
หาก Composable และคลาสตัวเก็บสถานะธรรมดามีหน้าที่รับผิดชอบตรรกะ UI และสถานะองค์ประกอบ UI ตัวเก็บสถานะระดับหน้าจอจะมีหน้าที่รับผิดชอบงานต่อไปนี้
- ให้สิทธิ์เข้าถึงตรรกะทางธุรกิจของแอปพลิเคชัน ซึ่งโดยปกติจะอยู่ในเลเยอร์อื่นๆ ในลำดับชั้น เช่น เลเยอร์ธุรกิจและ เลเยอร์ข้อมูล
- เตรียมข้อมูลแอปพลิเคชันสำหรับการนำเสนอในหน้าจอที่เฉพาะเจาะจง ซึ่งจะกลายเป็นสถานะ UI ของหน้าจอ
ViewModel เป็นเจ้าของสถานะ
ประโยชน์ของ AAC ViewModel ในการพัฒนา Android ทำให้ ViewModel เหมาะสม สำหรับการให้สิทธิ์เข้าถึงตรรกะทางธุรกิจและการเตรียมข้อมูลแอปพลิเคชัน สำหรับการนำเสนอบนหน้าจอ
เมื่อยกสถานะ UI ขึ้นใน ViewModel คุณจะย้ายสถานะนั้นออกจาก Composition
ViewModel จะจัดเก็บไว้นอก Compositionระบบจะไม่จัดเก็บ ViewModel เป็นส่วนหนึ่งของ Composition โดยเฟรมเวิร์กจะเป็นผู้จัดหา ViewModel และ ViewModel จะจำกัดขอบเขตไว้ที่ ViewModelStoreOwner ซึ่งอาจเป็น
Activity, Fragment, กราฟการนำทาง หรือปลายทางของกราฟการนำทาง ดูข้อมูลเพิ่มเติมเกี่ยวกับขอบเขต
ViewModelได้ในเอกสารประกอบ
จากนั้น ViewModel จะเป็นแหล่งข้อมูลที่เชื่อถือได้และบรรพบุรุษร่วมที่ต่ำที่สุด สำหรับสถานะ UI
สถานะ UI ของหน้าจอ
ตามคำจำกัดความข้างต้น สถานะ UI ของหน้าจอเกิดจากการใช้กฎทางธุรกิจ เนื่องจากตัวเก็บสถานะระดับหน้าจอมีหน้าที่รับผิดชอบสถานะ UI ของหน้าจอ ซึ่งหมายความว่าโดยปกติแล้วสถานะ UI ของหน้าจอจะถูกยกขึ้นในตัวเก็บสถานะระดับหน้าจอ ซึ่งในกรณีนี้คือ ViewModel
ลองดู ConversationViewModel ของแอปแชทและวิธีที่ ViewModel แสดงสถานะ UI ของหน้าจอและเหตุการณ์ต่างๆ เพื่อแก้ไขสถานะนั้น
class ConversationViewModel( channelId: String, messagesRepository: MessagesRepository ) : ViewModel() { val messages = messagesRepository .getLatestMessages(channelId) .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5_000), initialValue = emptyList() ) // Business logic fun sendMessage(message: Message) { /* ... */ } }
Composable จะใช้สถานะ UI ของหน้าจอที่ยกขึ้นใน ViewModel คุณควรแทรกอินสแตนซ์ ViewModel ใน Composable ระดับหน้าจอเพื่อให้สิทธิ์เข้าถึงตรรกะทางธุรกิจ
ต่อไปนี้เป็นตัวอย่าง ViewModel ที่ใช้ใน Composable ระดับหน้าจอ
ในตัวอย่างนี้ Composable ConversationScreen() จะใช้สถานะ UI ของหน้าจอที่ยกขึ้นใน ViewModel
@Composable private fun ConversationScreen( conversationViewModel: ConversationViewModel = viewModel() ) { val messages by conversationViewModel.messages.collectAsStateWithLifecycle() ConversationScreen( messages = messages, onSendMessage = { message: Message -> conversationViewModel.sendMessage(message) } ) } @Composable private fun ConversationScreen( messages: List<Message>, onSendMessage: (Message) -> Unit ) { MessagesList(messages, onSendMessage) /* ... */ }
การส่งต่อพร็อพเพอร์ตี้
"การส่งต่อพร็อพเพอร์ตี้" หมายถึงการส่งข้อมูลผ่านคอมโพเนนต์ย่อยที่ซ้อนกันหลายรายการไปยังตำแหน่งที่จะอ่านข้อมูลนั้น
ตัวอย่างทั่วไปที่การส่งต่อพร็อพเพอร์ตี้อาจปรากฏใน Compose คือเมื่อคุณแทรกตัวเก็บสถานะระดับหน้าจอที่ระดับบนสุดและส่งสถานะและเหตุการณ์ต่างๆ ไปยัง Composable ย่อย ซึ่งอาจสร้างการโอเวอร์โหลดของลายเซ็นฟังก์ชัน Composable เพิ่มเติมด้วย
แม้ว่าการแสดงเหตุการณ์เป็นพารามิเตอร์แลมดาแต่ละรายการอาจโอเวอร์โหลดลายเซ็นฟังก์ชัน แต่ก็ช่วยให้มองเห็นความรับผิดชอบของฟังก์ชัน Composable ได้สูงสุด คุณจะเห็นสิ่งที่ฟังก์ชันทำได้อย่างรวดเร็ว
การส่งต่อพร็อพเพอร์ตี้เป็นวิธีที่แนะนำมากกว่าการสร้างคลาส Wrapper เพื่อห่อหุ้มสถานะและเหตุการณ์ต่างๆ ไว้ในที่เดียว เนื่องจากวิธีนี้จะลดการมองเห็นความรับผิดชอบของ Composable การไม่ใช้คลาส Wrapper ยังทำให้คุณมีแนวโน้มที่จะส่งเฉพาะพารามิเตอร์ที่ Composable ต้องการ ซึ่งเป็นแนวทางปฏิบัติแนะนำ
แนวทางปฏิบัติแนะนำเดียวกันนี้ใช้ได้หากเหตุการณ์เหล่านี้เป็นเหตุการณ์การนำทาง โดยคุณสามารถ ดูข้อมูลเพิ่มเติมได้ในเอกสารประกอบการนำทาง
หากพบปัญหาด้านประสิทธิภาพ คุณอาจเลือกที่จะเลื่อนการอ่านสถานะออกไป ดูข้อมูลเพิ่มเติมได้ในเอกสารประกอบด้านประสิทธิภาพ
สถานะองค์ประกอบ UI
คุณสามารถยกสถานะองค์ประกอบ UI ขึ้นไปยังตัวยึดสถานะระดับหน้าจอได้หากมีตรรกะทางธุรกิจที่ต้องอ่านหรือเขียนสถานะนั้น
จากตัวอย่างแอปแชท แอปจะแสดงคำแนะนำผู้ใช้ในการแชทเป็นกลุ่มเมื่อผู้ใช้พิมพ์ @ และคำแนะนำ คำแนะนำเหล่านั้นมาจากเลเยอร์ข้อมูล และตรรกะในการคำนวณรายการคำแนะนำผู้ใช้ถือเป็นตรรกะทางธุรกิจ ฟีเจอร์นี้มีลักษณะดังนี้
@ และคำแนะนำViewModel ที่ใช้ฟีเจอร์นี้จะมีลักษณะดังนี้
class ConversationViewModel(/*...*/) : ViewModel() { // Hoisted state var inputMessage by mutableStateOf("") private set val suggestions: StateFlow<List<Suggestion>> = snapshotFlow { inputMessage } .filter { hasSocialHandleHint(it) } .mapLatest { getHandle(it) } .mapLatest { repository.getSuggestions(it) } .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5_000), initialValue = emptyList() ) fun updateInput(newInput: String) { inputMessage = newInput } }
inputMessage เป็นตัวแปรที่จัดเก็บสถานะ TextField ทุกครั้งที่ผู้ใช้พิมพ์ข้อมูลใหม่ แอปจะเรียกใช้ตรรกะทางธุรกิจเพื่อสร้าง suggestions
suggestions คือสถานะ UI ของหน้าจอและใช้จาก Compose UI โดยการรวบรวม
จาก StateFlow
ข้อควรระวัง
สำหรับสถานะองค์ประกอบ UI บางอย่างของ Compose การยกสถานะขึ้นไปยัง ViewModel อาจต้องพิจารณาเป็นพิเศษ เช่น ตัวยึดสถานะบางตัวขององค์ประกอบ UI ของ Compose จะแสดงเมธอดเพื่อแก้ไขสถานะ โดยบางเมธอดอาจเป็นฟังก์ชันระงับที่ทริกเกอร์ภาพเคลื่อนไหว ฟังก์ชันระงับเหล่านี้อาจแสดงข้อยกเว้นหากคุณเรียกใช้
จาก CoroutineScope ที่ไม่ได้จำกัดขอบเขตไว้ที่
Composition
สมมติว่าเนื้อหาของลิ้นชักแอปเป็นแบบไดนามิก และคุณต้องดึงข้อมูลและรีเฟรชเนื้อหาจากเลเยอร์ข้อมูลหลังจากปิดลิ้นชักแล้ว คุณควรยกสถานะลิ้นชักขึ้นไปยัง ViewModel เพื่อให้เรียกใช้ทั้งตรรกะ UI และตรรกะทางธุรกิจในองค์ประกอบนี้จากเจ้าของสถานะได้
อย่างไรก็ตาม การเรียกใช้เมธอด close() ของ DrawerState โดยใช้
viewModelScope จาก Compose UI จะทำให้เกิดข้อยกเว้นรันไทม์ประเภท
IllegalStateException พร้อมข้อความว่า “a
MonotonicFrameClock is not available in this
CoroutineContext”.
หากต้องการแก้ไขปัญหานี้ ให้ใช้ CoroutineScope ที่จำกัดขอบเขตไว้ที่ Composition ซึ่งจะให้ MonotonicFrameClock ใน CoroutineContext ที่จำเป็นต่อการทำงานของฟังก์ชันระงับ
หากต้องการแก้ไขข้อขัดข้องนี้ ให้เปลี่ยน CoroutineContext ของ Coroutine ใน ViewModel เป็น CoroutineContext ที่จำกัดขอบเขตไว้ที่ Composition ซึ่งอาจมีลักษณะดังนี้
class ConversationViewModel(/*...*/) : ViewModel() { val drawerState = DrawerState(initialValue = DrawerValue.Closed) private val _drawerContent = MutableStateFlow(DrawerContent.Empty) val drawerContent: StateFlow<DrawerContent> = _drawerContent.asStateFlow() fun closeDrawer(uiScope: CoroutineScope) { viewModelScope.launch { withContext(uiScope.coroutineContext) { // Use instead of the default context drawerState.close() } // Fetch drawer content and update state _drawerContent.update { content } } } } // in Compose @Composable private fun ConversationScreen( conversationViewModel: ConversationViewModel = viewModel() ) { val scope = rememberCoroutineScope() ConversationScreen(onCloseDrawer = { conversationViewModel.closeDrawer(uiScope = scope) }) }
ดูข้อมูลเพิ่มเติม
ดูข้อมูลเพิ่มเติมเกี่ยวกับสถานะและ Jetpack Compose ได้จากแหล่งข้อมูลเพิ่มเติมต่อไปนี้
ตัวอย่าง
Codelab
วิดีโอ
แนะนำสำหรับคุณ
- หมายเหตุ: ข้อความลิงก์จะแสดงเมื่อ JavaScript ปิดอยู่
- บันทึกสถานะ UI ใน Compose
- รายการและตารางกริด
- การออกแบบสถาปัตยกรรม UI ของ Compose