ในโหมดเขียน UI จะเปลี่ยนแปลงไม่ได้ คุณจะอัปเดต UI ไม่ได้หลังจากวาดแล้ว สิ่งที่คุณควบคุมได้คือสถานะของ UI ทุกครั้งที่สถานะของ UI เปลี่ยนแปลง Compose จะสร้างส่วนของต้นไม้ UI ที่เปลี่ยนแปลงแล้วขึ้นมาใหม่ คอมโพสิเบิลสามารถยอมรับสถานะและแสดงเหตุการณ์ได้ เช่น TextField
จะยอมรับค่าและแสดง onValueChange
ที่เป็นคอลแบ็กซึ่งส่งคําขอไปยังตัวแฮนเดิลคอลแบ็กให้เปลี่ยนค่า
var name by remember { mutableStateOf("") } OutlinedTextField( value = name, onValueChange = { name = it }, label = { Text("Name") } )
เนื่องจากคอมโพสิเบิลยอมรับสถานะและแสดงเหตุการณ์ รูปแบบการไหลของข้อมูลแบบทิศทางเดียวจึงเหมาะกับ Jetpack Compose คู่มือนี้มุ่งเน้นที่วิธีใช้รูปแบบการไหลของข้อมูลแบบทิศทางเดียวใน Compose, วิธีใช้เหตุการณ์และผู้เก็บสถานะ และวิธีใช้ ViewModel ใน Compose
โฟลว์ข้อมูลที่เป็นแบบทิศทางเดียว
การไหลของข้อมูลแบบทิศทางเดียว (UDF) คือรูปแบบการออกแบบที่สถานะจะไหลลงและเหตุการณ์จะไหลขึ้น เมื่อใช้การไหลของข้อมูลแบบทิศทางเดียว คุณจะแยกคอมโพสิเบิลที่แสดงสถานะใน UI ออกจากส่วนต่างๆ ของแอปที่เก็บและเปลี่ยนแปลงสถานะได้
ลูปการอัปเดต UI สําหรับแอปที่ใช้การไหลของข้อมูลแบบทิศทางเดียวมีลักษณะดังนี้
- เหตุการณ์: UI บางส่วนสร้างเหตุการณ์และส่งต่อไปยังด้านบน เช่น การคลิกปุ่มที่ส่งไปยัง ViewModel เพื่อจัดการ หรือมีการส่งเหตุการณ์จากเลเยอร์อื่นๆ ของแอป เช่น ระบุว่าเซสชันของผู้ใช้หมดอายุแล้ว
- อัปเดตสถานะ: ตัวแฮนเดิลเหตุการณ์อาจเปลี่ยนสถานะ
- แสดงสถานะ: ตัวเก็บสถานะจะส่งต่อสถานะ และ UI จะแสดงสถานะนั้น

การใช้รูปแบบนี้เมื่อใช้ Jetpack Compose มีข้อดีหลายประการ ดังนี้
- ความสามารถในการทดสอบ: การแยกสถานะออกจาก UI ที่แสดงสถานะช่วยให้ทดสอบทั้ง 2 อย่างแยกกันได้ง่ายขึ้น
- การรวมสถานะ: เนื่องจากสถานะจะอัปเดตได้ในที่เดียวเท่านั้น และมีแหล่งที่มาของข้อมูลสถานะของคอมโพสิเบิลเพียงแหล่งเดียว คุณจึงมีโอกาสที่จะสร้างข้อบกพร่องเนื่องจากสถานะไม่สอดคล้องกันน้อยลง
- ความสอดคล้องของ UI: การอัปเดตสถานะทั้งหมดจะแสดงใน UI ทันทีด้วยการใช้ตัวเก็บสถานะที่สังเกตได้ เช่น
StateFlow
หรือLiveData
การรับส่งข้อมูลแบบทิศทางเดียวใน Jetpack Compose
คอมโพสิเบิลจะทํางานตามสถานะและเหตุการณ์ ตัวอย่างเช่น TextField
จะอัปเดตก็ต่อเมื่อมีการอัปเดตพารามิเตอร์ value
และมีการเปิดเผย onValueChange
callback ซึ่งเป็นเหตุการณ์ที่ขอให้เปลี่ยนค่าเป็นค่าใหม่ คอมโพซจะกำหนดออบเจ็กต์ State
เป็นตัวเก็บค่า และการเปลี่ยนแปลงค่าสถานะจะทริกเกอร์การคอมโพซใหม่ คุณสามารถเก็บสถานะไว้ใน remember { mutableStateOf(value) }
หรือ rememberSaveable { mutableStateOf(value)
โดยขึ้นอยู่กับระยะเวลาที่ต้องการจดจำค่า
ประเภทของค่าคอมโพสิชัน TextField
คือ String
ดังนั้นค่านี้จึงมาจากที่ใดก็ได้ ไม่ว่าจะเป็นค่าที่ฮาร์ดโค้ดไว้ จาก ViewModel หรือส่งมาจากคอมโพสิชันหลัก คุณไม่จำเป็นต้องเก็บค่าไว้ในออบเจ็กต์ State
แต่ต้องอัปเดตค่าเมื่อเรียกใช้ onValueChange
กําหนดพารามิเตอร์แบบคอมโพสิเบิล
เมื่อกําหนดพารามิเตอร์สถานะของคอมโพสิเบิล คุณควรคำนึงถึงคำถามต่อไปนี้
- คอมโพสิเบิลนั้นนํากลับมาใช้ซ้ำหรือยืดหยุ่นเพียงใด
- พารามิเตอร์สถานะส่งผลต่อประสิทธิภาพของคอมโพสิเบิลนี้อย่างไร
คอมโพสิเบิลแต่ละรายการควรมีข้อมูลน้อยที่สุดเพื่อส่งเสริมการแยกและการนํากลับมาใช้ใหม่ เช่น เมื่อสร้างคอมโพสิเบิลเพื่อเก็บส่วนหัวของบทความข่าว ให้ส่งเฉพาะข้อมูลที่จำเป็นต้องแสดงแทนการส่งทั้งบทความข่าว
@Composable fun Header(title: String, subtitle: String) { // Recomposes when title or subtitle have changed. } @Composable fun Header(news: News) { // Recomposes when a new instance of News is passed in. }
บางครั้งการใช้พารามิเตอร์แต่ละรายการก็ช่วยปรับปรุงประสิทธิภาพได้เช่นกัน เช่น หาก News
มีข้อมูลมากกว่า title
และ subtitle
เมื่อใดก็ตามที่มีการส่งอินสแตนซ์ใหม่ของ News
ไปยัง Header(news)
คอมโพสิเบิลจะคอมไพล์อีกครั้ง แม้ว่า title
และ subtitle
จะไม่มีการเปลี่ยนแปลง
พิจารณาจำนวนพารามิเตอร์ที่คุณส่งอย่างรอบคอบ การมีฟังก์ชันที่มีพารามิเตอร์มากเกินไปจะทำให้ฟังก์ชันใช้งานยาก ดังนั้นในกรณีนี้ เราขอแนะนำให้จัดกลุ่มพารามิเตอร์ไว้ในคลาส
เหตุการณ์ในเครื่องมือเขียน
อินพุตทั้งหมดในแอปควรแสดงเป็นเหตุการณ์ เช่น การแตะ การเปลี่ยนแปลงข้อความ และแม้กระทั่งตัวจับเวลาหรือการอัปเดตอื่นๆ เนื่องจากเหตุการณ์เหล่านี้จะเปลี่ยนสถานะของ UI ViewModel
จึงควรเป็นผู้จัดการเหตุการณ์และอัปเดตสถานะของ UI
เลเยอร์ UI ไม่ควรเปลี่ยนสถานะนอกตัวจัดการเหตุการณ์ เนื่องจากอาจทำให้เกิดความไม่สอดคล้องและข้อบกพร่องในแอปพลิเคชัน
โปรดส่งค่าแบบคงที่สำหรับ Lambda สถานะและตัวแฮนเดิลเหตุการณ์ แนวทางนี้มีข้อดีดังนี้
- คุณปรับปรุงความสามารถในการนํากลับมาใช้ใหม่
- ตรวจสอบว่า UI ของคุณไม่ได้เปลี่ยนค่าของสถานะโดยตรง
- คุณหลีกเลี่ยงปัญหาการเรียกใช้พร้อมกันได้เนื่องจากตรวจสอบว่าสถานะไม่ได้เปลี่ยนแปลงจากเธรดอื่น
- บ่อยครั้งที่คุณลดความซับซ้อนของโค้ด
เช่น คอมโพสิเบิลที่ยอมรับ String
และ Lambda เป็นพารามิเตอร์สามารถเรียกใช้ได้จากบริบทหลายรายการและนํากลับมาใช้ซ้ำได้อย่างมาก สมมติว่าแถบแอปด้านบนในแอปแสดงข้อความและมีปุ่มย้อนกลับเสมอ คุณสามารถกําหนดคอมโพสิชัน MyAppTopAppBar
ทั่วไปมากขึ้นซึ่งรับข้อความและตัวแฮนเดิลปุ่มย้อนกลับเป็นพารามิเตอร์ ดังนี้
@Composable fun MyAppTopAppBar(topAppBarText: String, onBackPressed: () -> Unit) { TopAppBar( title = { Text( text = topAppBarText, textAlign = TextAlign.Center, modifier = Modifier .fillMaxSize() .wrapContentSize(Alignment.Center) ) }, navigationIcon = { IconButton(onClick = onBackPressed) { Icon( Icons.AutoMirrored.Filled.ArrowBack, contentDescription = localizedString ) } }, // ... ) }
ตัวอย่าง ViewModel, สถานะ และเหตุการณ์
เมื่อใช้ ViewModel
และ mutableStateOf
คุณยังนํา Dataflow แบบทิศทางเดียวมาใช้ในแอปได้ด้วยหากเงื่อนไขข้อใดข้อหนึ่งต่อไปนี้เป็นจริง
- สถานะของ UI จะแสดงผ่านตัวเก็บสถานะที่สังเกตได้ เช่น
StateFlow
หรือLiveData
ViewModel
จะจัดการเหตุการณ์ที่มาจาก UI หรือเลเยอร์อื่นๆ ของแอป และอัปเดตตัวเก็บสถานะตามเหตุการณ์
ตัวอย่างเช่น เมื่อใช้หน้าจอลงชื่อเข้าใช้ การแตะปุ่มลงชื่อเข้าใช้ควรทําให้แอปแสดงแถบเลื่อนความคืบหน้าและการเรียกใช้เครือข่าย หากเข้าสู่ระบบสําเร็จ แอปจะไปยังหน้าจออื่น ในกรณีที่เกิดข้อผิดพลาด แอปจะแสดง Snackbar วิธีจำลองสถานะหน้าจอและเหตุการณ์มีดังนี้
หน้าจอมี 4 สถานะ ดังนี้
- ออกจากระบบ: เมื่อผู้ใช้ยังไม่ได้ลงชื่อเข้าใช้
- กำลังดำเนินการ: เมื่อแอปพยายามลงชื่อเข้าใช้ผู้ใช้โดยเรียกใช้เครือข่าย
- ข้อผิดพลาด: เมื่อเกิดข้อผิดพลาดขณะลงชื่อเข้าใช้
- ลงชื่อเข้าใช้: เมื่อผู้ใช้ลงชื่อเข้าใช้
คุณจําลองสถานะเหล่านี้เป็นคลาสที่ปิดได้ ViewModel
จะแสดงสถานะเป็น State
กําหนดสถานะเริ่มต้น และอัปเดตสถานะตามต้องการ ViewModel
ยังจัดการเหตุการณ์การลงชื่อเข้าใช้ด้วยการแสดงเมธอด onSignIn()
class MyViewModel : ViewModel() { private val _uiState = mutableStateOf<UiState>(UiState.SignedOut) val uiState: State<UiState> get() = _uiState // ... }
นอกเหนือจาก mutableStateOf
API แล้ว Compose ยังมีส่วนขยายสําหรับ LiveData
, Flow
และ Observable
เพื่อลงทะเบียนเป็นผู้ฟังและแสดงค่าเป็นสถานะ
class MyViewModel : ViewModel() { private val _uiState = MutableLiveData<UiState>(UiState.SignedOut) val uiState: LiveData<UiState> get() = _uiState // ... } @Composable fun MyComposable(viewModel: MyViewModel) { val uiState = viewModel.uiState.observeAsState() // ... }
ดูข้อมูลเพิ่มเติม
ดูข้อมูลเพิ่มเติมเกี่ยวกับสถาปัตยกรรมใน Jetpack Compose ได้ที่แหล่งข้อมูลต่อไปนี้
ตัวอย่าง
แนะนำสำหรับคุณ
- หมายเหตุ: ข้อความลิงก์จะแสดงเมื่อ JavaScript ปิดอยู่
- State และ Jetpack Compose
- บันทึกสถานะ UI ใน Compose
- จัดการอินพุตของผู้ใช้