การจัดโครงสร้าง UI ของ Compose

ในโหมดเขียน 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 สําหรับแอปที่ใช้การไหลของข้อมูลแบบทิศทางเดียวมีลักษณะดังนี้

  1. เหตุการณ์: UI บางส่วนสร้างเหตุการณ์และส่งต่อไปยังด้านบน เช่น การคลิกปุ่มที่ส่งไปยัง ViewModel เพื่อจัดการ หรือมีการส่งเหตุการณ์จากเลเยอร์อื่นๆ ของแอป เช่น ระบุว่าเซสชันของผู้ใช้หมดอายุแล้ว
  2. อัปเดตสถานะ: ตัวแฮนเดิลเหตุการณ์อาจเปลี่ยนสถานะ
  3. แสดงสถานะ: ตัวเก็บสถานะจะส่งต่อสถานะ และ UI จะแสดงสถานะนั้น

เหตุการณ์จะไหลขึ้นจาก UI ไปยังตัวเก็บสถานะ และสถานะจะไหลลงจากตัวเก็บสถานะไปยัง UI
รูปที่ 1 โฟลว์ข้อมูลที่เป็นแบบทิศทางเดียว

การใช้รูปแบบนี้เมื่อใช้ 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 ได้ที่แหล่งข้อมูลต่อไปนี้

ตัวอย่าง