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