ใน Compose นั้น UI จะเปลี่ยนแปลงไม่ได้ กล่าวคือไม่มีวิธีอัปเดตหลังจากที่วาดแล้ว สิ่งที่คุณควบคุมได้คือสถานะของ UI ทุกครั้งที่สถานะของ UI เปลี่ยนแปลง Compose จะสร้างส่วนต่างๆ ของ UI Tree ที่มีการเปลี่ยนแปลงขึ้นมาใหม่ คอมโพสิเบิลสามารถรับสถานะและแสดงเหตุการณ์ได้ เช่น TextField จะรับค่าและแสดง Callback onValueChange ที่ขอให้ Callback Handler เปลี่ยนค่า
var name by remember { mutableStateOf("") } OutlinedTextField( value = name, onValueChange = { name = it }, label = { Text("Name") } )
เนื่องจาก Composable ยอมรับสถานะและแสดงเหตุการณ์ รูปแบบการไหลของข้อมูลแบบทิศทางเดียวจึงเหมาะกับ Jetpack Compose เป็นอย่างดี คู่มือนี้จะเน้นที่วิธีใช้รูปแบบโฟลว์ข้อมูลแบบทิศทางเดียวใน Compose, วิธีใช้เหตุการณ์และตัวเก็บสถานะ และวิธีใช้ ViewModel ใน Compose
โฟลว์ข้อมูลแบบทิศทางเดียว
โฟลว์ข้อมูลแบบทิศทางเดียว (UDF) เป็นรูปแบบการออกแบบที่สถานะจะไหลลงและเหตุการณ์จะไหลขึ้น การทำตามโฟลว์ข้อมูลแบบทิศทางเดียวจะช่วยให้คุณแยก Composable ที่แสดงสถานะใน UI ออกจากส่วนต่างๆ ของแอปที่จัดเก็บและเปลี่ยนแปลงสถานะได้
ลูปการอัปเดต UI สำหรับแอปที่ใช้โฟลว์ข้อมูลแบบทิศทางเดียวจะมีลักษณะดังนี้
- เหตุการณ์: ส่วนหนึ่งของ UI จะสร้างเหตุการณ์และส่งต่อขึ้นไป เช่น การคลิกปุ่มที่ส่งไปยัง ViewModel เพื่อจัดการ หรือเหตุการณ์ที่ส่งมาจากเลเยอร์อื่นๆ ของแอป เช่น การระบุว่าเซสชันของผู้ใช้หมดอายุแล้ว
- อัปเดตสถานะ: ตัวแฮนเดิลเหตุการณ์อาจเปลี่ยนสถานะ
- สถานะการแสดงผล: ตัวเก็บสถานะจะส่งต่อสถานะ และ UI จะแสดงสถานะนั้น
การทำตามรูปแบบนี้เมื่อใช้ Jetpack Compose มีข้อดีหลายประการดังนี้
- ความสามารถในการทดสอบ: การแยกสถานะออกจาก UI ที่แสดงสถานะจะช่วยให้ทดสอบทั้ง 2 อย่างแยกกันได้ง่ายขึ้น
- การรวมสถานะ: เนื่องจากสถานะสามารถอัปเดตได้ในที่เดียวและมีแหล่งข้อมูลที่ถูกต้องเพียงแหล่งเดียวสำหรับสถานะของ Composable คุณจึงมีโอกาสน้อยที่จะสร้างข้อบกพร่องเนื่องจากสถานะที่ไม่สอดคล้องกัน
- ความสอดคล้องของ UI: การอัปเดตสถานะทั้งหมดจะแสดงใน UI ทันทีโดยใช้ตัวเก็บสถานะที่สังเกตได้ เช่น
StateFlowหรือLiveData
โฟลว์ข้อมูลแบบทิศทางเดียวใน Jetpack Compose
Composable ทำงานตามสถานะและเหตุการณ์ ตัวอย่างเช่น TextField จะอัปเดตเมื่อมีการอัปเดตพารามิเตอร์ value เท่านั้น และจะแสดง Callback onValueChange ซึ่งเป็นเหตุการณ์ที่ขอให้เปลี่ยนค่าเป็นค่าใหม่ Compose จะกำหนดออบเจ็กต์ State เป็นตัวยึดค่า และการเปลี่ยนแปลงค่าสถานะจะทริกเกอร์การจัดองค์ประกอบใหม่ คุณสามารถเก็บสถานะไว้ใน remember { mutableStateOf(value) } หรือ rememberSaveable { mutableStateOf(value) } ได้ โดยขึ้นอยู่กับระยะเวลาที่คุณต้องการจดจำค่า
ประเภทของค่าของ TextField composable คือ String ดังนั้นค่านี้จึงมาจากที่ใดก็ได้ ไม่ว่าจะเป็นค่าที่ฮาร์ดโค้ด จาก ViewModel หรือส่งมาจาก composable ระดับบนสุด คุณไม่จำเป็นต้องเก็บไว้ในออบเจ็กต์ State แต่ต้องอัปเดตค่าเมื่อมีการเรียกใช้ onValueChange
mutableStateOf(value)จะสร้างMutableStateซึ่งเป็นประเภทที่สังเกตได้ใน Compose การเปลี่ยนแปลงค่าจะกำหนดเวลาการจัดองค์ประกอบใหม่ของฟังก์ชันที่ประกอบกันได้ซึ่งอ่านค่านั้นrememberจะจัดเก็บออบเจ็กต์ไว้ใน Composition และจะลืมออบเจ็กต์เมื่อนำ Composable ที่เรียกใช้rememberออกจาก CompositionrememberSaveableจะคงสถานะไว้ตลอดการเปลี่ยนแปลงการกำหนดค่าโดยบันทึกไว้ในBundle
กำหนดพารามิเตอร์ Composable
เมื่อกำหนดพารามิเตอร์สถานะของ Composable ให้คำนึงถึงคำถามต่อไปนี้
- Composable มีความยืดหยุ่นหรือนำกลับมาใช้ใหม่ได้มากน้อยเพียงใด
- พารามิเตอร์สถานะส่งผลต่อประสิทธิภาพของ Composable นี้อย่างไร
เพื่อส่งเสริมการแยกส่วนและการนำกลับมาใช้ใหม่ Composable แต่ละรายการควรเก็บข้อมูลให้น้อยที่สุดเท่าที่จะเป็นไปได้ ตัวอย่างเช่น เมื่อสร้าง Composable เพื่อเก็บส่วนหัวของบทความข่าว ให้เลือกส่งเฉพาะข้อมูลที่ต้องแสดง แทนที่จะส่งบทความข่าวทั้งบทความ
@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 จะไม่มีการเปลี่ยนแปลงก็ตาม
พิจารณาจำนวนพารามิเตอร์ที่คุณส่งอย่างรอบคอบ การมีฟังก์ชันที่มีพารามิเตอร์มากเกินไปจะลดประสิทธิภาพการทำงานของฟังก์ชัน ดังนั้นในกรณีนี้ การจัดกลุ่มพารามิเตอร์ไว้ในคลาสจึงเป็นวิธีที่เหมาะสมกว่า
เหตุการณ์ใน Compose
อินพุตทุกรายการในแอปควรแสดงเป็นเหตุการณ์ เช่น การแตะ การเปลี่ยนแปลงข้อความ และแม้แต่ตัวจับเวลาหรือการอัปเดตอื่นๆ เนื่องจากเหตุการณ์เหล่านี้เปลี่ยนสถานะของ UI
ViewModel ควรจัดการเหตุการณ์และอัปเดตสถานะ UI
เลเยอร์ UI ไม่ควรเปลี่ยนสถานะนอกตัวแฮนเดิลเหตุการณ์ เนื่องจากอาจทำให้เกิดความไม่สอดคล้องกันและข้อบกพร่องในแอปพลิเคชัน
แนะนำให้ส่งค่าที่เปลี่ยนแปลงไม่ได้สำหรับ Lambda ของเครื่องจัดการสถานะและเหตุการณ์ วิธีนี้มีประโยชน์ดังต่อไปนี้
- คุณปรับปรุงความสามารถในการนำกลับมาใช้ใหม่
- คุณยืนยันว่า UI ของคุณไม่ได้เปลี่ยนค่าของสถานะโดยตรง
- คุณหลีกเลี่ยงปัญหาการทำงานพร้อมกันได้เนื่องจากคุณตรวจสอบว่าสถานะไม่ได้เปลี่ยนแปลงจากเทรดอื่น
- ซึ่งมักจะช่วยลดความซับซ้อนของโค้ดได้
ตัวอย่างเช่น Composable ที่ยอมรับ String และ Lambda เป็นพารามิเตอร์สามารถเรียกใช้จากบริบทต่างๆ ได้และนำกลับมาใช้ซ้ำได้สูง สมมติว่าแถบแอปด้านบนในแอปแสดงข้อความและมีปุ่มย้อนกลับเสมอ คุณสามารถกำหนด MyAppTopAppBar Composable ที่เป็นแบบทั่วไปมากขึ้นซึ่งรับข้อความและเครื่องจัดการปุ่มย้อนกลับเป็นพารามิเตอร์ได้ดังนี้
@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 คุณยังสามารถแนะนำโฟลว์ข้อมูลแบบทิศทางเดียวในแอปได้หากข้อใดข้อหนึ่งต่อไปนี้เป็นจริง
- สถานะของ UI จะแสดงโดยใช้ตัวเก็บสถานะที่สังเกตได้ เช่น
StateFlowหรือLiveData ViewModelจะจัดการเหตุการณ์ที่มาจาก UI หรือเลเยอร์อื่นๆ ของแอป และอัปเดตตัวเก็บสถานะตามเหตุการณ์
ตัวอย่างเช่น เมื่อใช้หน้าจอลงชื่อเข้าใช้ การแตะปุ่มลงชื่อเข้าใช้ควรทำให้แอปแสดงภาพหมุนแสดงความคืบหน้าและการเรียกใช้เครือข่าย หากเข้าสู่ระบบสำเร็จ แอปจะไปยังหน้าจออื่น แต่หากเกิดข้อผิดพลาด แอปจะแสดง Snackbar ต่อไปนี้คือวิธีจำลองสถานะหน้าจอและเหตุการณ์
หน้าจอมี 4 สถานะ ดังนี้
- ออกจากระบบ: เมื่อผู้ใช้ยังไม่ได้ลงชื่อเข้าใช้
- กำลังดำเนินการ: เมื่อแอปพยายามลงชื่อเข้าใช้ให้ผู้ใช้โดยทำการเรียกใช้เครือข่าย
- Error: เมื่อเกิดข้อผิดพลาดขณะลงชื่อเข้าใช้
- ลงชื่อเข้าใช้: เมื่อผู้ใช้ลงชื่อเข้าใช้
คุณสามารถจำลองสถานะเหล่านี้เป็นคลาสที่ปิดผนึกได้ 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 เพื่อลงทะเบียนเป็น Listener และแสดงค่าเป็นสถานะ
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 ปิดอยู่
- สถานะและ Jetpack Compose
- บันทึกสถานะ UI ใน Compose
- จัดการอินพุตของผู้ใช้