ใน Compose นั้น UI จะเปลี่ยนแปลงไม่ได้ ซึ่งหมายความว่าคุณจะอัปเดต UI หลังจากวาดแล้วไม่ได้
สิ่งที่คุณควบคุมได้คือสถานะของ UI ทุกครั้งที่สถานะของ
UI เปลี่ยนไป Compose จะสร้างส่วนของแผนผัง UI ที่มีการเปลี่ยนแปลงขึ้นมาใหม่ Composable สามารถรับสถานะและแสดงเหตุการณ์ได้ เช่น 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 และแสดงการเรียกกลับ onValueChange
ซึ่งเป็นเหตุการณ์ที่ขอให้เปลี่ยนค่าเป็นค่าใหม่ Compose
กำหนดStateออบเจ็กต์เป็นตัวยึดค่า และการเปลี่ยนแปลงค่าสถานะ
จะทริกเกอร์การจัดองค์ประกอบใหม่ คุณสามารถเก็บสถานะไว้ใน
remember { mutableStateOf(value) } หรือ
rememberSaveable { mutableStateOf(value) ได้โดยขึ้นอยู่กับระยะเวลาที่ต้องการ
จดจำค่า
ประเภทของค่าของ TextField composable คือ String ดังนั้นค่านี้จึงมาจากที่ใดก็ได้ ไม่ว่าจะเป็นค่าที่ฮาร์ดโค้ด ค่าจาก ViewModel หรือค่าที่ส่งมาจาก
Composable ระดับบน คุณไม่จำเป็นต้องเก็บไว้ในออบเจ็กต์ State แต่ต้องอัปเดตค่าเมื่อมีการเรียกใช้ onValueChange
กำหนดพารามิเตอร์ที่ใช้ร่วมกันได้
เมื่อกำหนดพารามิเตอร์สถานะของ 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 สถานะ ได้แก่
- ออกจากระบบ: เมื่อผู้ใช้ยังไม่ได้ลงชื่อเข้าใช้
- กำลังดำเนินการ: เมื่อแอปพยายามลงชื่อเข้าใช้ให้ผู้ใช้โดย ทำการเรียกเครือข่าย
- ข้อผิดพลาด: เมื่อเกิดข้อผิดพลาดขณะลงชื่อเข้าใช้
- ลงชื่อเข้าใช้: เมื่อผู้ใช้ลงชื่อเข้าใช้
คุณสามารถสร้างโมเดลสถานะเหล่านี้เป็นคลาสที่ปิดผนึกได้ 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 ปิดอยู่
- สถานะและ Jetpack Compose
- บันทึกสถานะ UI ใน Compose
- จัดการข้อมูลจากผู้ใช้