สถาปัตยกรรม UI ของ Compose

ใน 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) คือรูปแบบการออกแบบที่สถานะไหลลง และเหตุการณ์ไหลขึ้น การใช้โฟลว์ข้อมูลแบบทิศทางเดียวจะช่วยให้คุณแยกส่วนประกอบที่แสดงสถานะใน UI ออกจากส่วนต่างๆ ของแอปที่จัดเก็บและเปลี่ยนสถานะได้

วงจรการอัปเดต UI สำหรับแอปที่ใช้การไหลของข้อมูลแบบทิศทางเดียวจะมีลักษณะดังนี้

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

การทำตามรูปแบบนี้เมื่อใช้ 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 จะไม่เปลี่ยนแปลงก็ตาม

โปรดพิจารณาจํานวนพารามิเตอร์ที่คุณส่งอย่างรอบคอบ การมีฟังก์ชันที่มีพารามิเตอร์มากเกินไปจะลดความสะดวกในการใช้งานของฟังก์ชัน ดังนั้นในกรณีนี้ จึงควรจัดกลุ่มพารามิเตอร์ไว้ในคลาส

เหตุการณ์ในฟีเจอร์เขียน

อินพุตทุกรายการในแอปควรแสดงเป็นเหตุการณ์ เช่น การแตะ การเปลี่ยนแปลงข้อความ และแม้แต่ตัวจับเวลาหรือการอัปเดตอื่นๆ เนื่องจากเหตุการณ์เหล่านี้จะเปลี่ยนสถานะของ 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 หรือเลเยอร์อื่นๆ ของแอป และอัปเดตที่เก็บสถานะตามเหตุการณ์

ตัวอย่างเช่น เมื่อติดตั้งใช้งานหน้าจอลงชื่อเข้าใช้ การแตะปุ่มลงชื่อเข้าใช้ ควรทำให้แอปแสดงวงกลมแสดงความคืบหน้าและเรียกใช้เครือข่าย หาก การเข้าสู่ระบบสำเร็จ แอปจะไปยังหน้าจออื่น ในกรณีที่ เกิดข้อผิดพลาด แอปจะแสดงแถบข้อความ วิธีสร้างโมเดลสถานะหน้าจอ และเหตุการณ์

หน้าจอมี 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 ได้ที่แหล่งข้อมูลต่อไปนี้

ตัวอย่าง