การเขียน

Jetpack Compose เป็นชุดเครื่องมือ UI สมัยใหม่ที่อธิบายอย่างชัดเจนสำหรับ Android Compose ช่วยให้การเขียนและดูแลรักษา UI ของแอปง่ายขึ้นด้วยการจัดเตรียม API แบบประกาศที่ช่วยให้คุณแสดงผล UI ของแอปได้โดยไม่ต้องเปลี่ยนมุมมองฟรอนท์เอนด์แบบบังคับ คำศัพท์นี้ต้องมีการอธิบายเพิ่มเติม แต่ความหมายนั้นสำคัญต่อการออกแบบแอปของคุณ

กระบวนทัศน์การเขียนโปรแกรมแบบประกาศ

ในอดีต ลำดับชั้นการแสดงผลของ Android สามารถแสดงเป็นโครงสร้างต้นไม้ของวิดเจ็ต UI เมื่อสถานะของแอปเปลี่ยนแปลงเนื่องจากสิ่งต่างๆ เช่น การโต้ตอบของผู้ใช้ คุณต้องอัปเดตลําดับชั้น UI เพื่อแสดงข้อมูลปัจจุบัน วิธีที่พบบ่อยที่สุดในการอัปเดต UI คือการเดินผ่านแผนผังโดยใช้ฟังก์ชันต่างๆ เช่น findViewById() และเปลี่ยนโหนดโดยเรียกใช้เมธอดต่างๆ เช่น button.setText(String), container.addChild(View) หรือ img.setImageBitmap(Bitmap) เมธอดเหล่านี้จะเปลี่ยนสถานะภายในของวิดเจ็ต

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

ในช่วงหลายปีที่ผ่านมา อุตสาหกรรมทั้งหมดได้เริ่มเปลี่ยนไปใช้โมเดล UI แบบประกาศ ซึ่งช่วยลดความซับซ้อนในการสร้างและอัปเดตอินเทอร์เฟซผู้ใช้

เทคนิคนี้ทำงานโดยการสร้างทั้งหน้าจอใหม่ตั้งแต่ต้นในเชิงแนวคิด จากนั้นจึงใช้เฉพาะการเปลี่ยนแปลงที่จำเป็น วิธีนี้ช่วยหลีกเลี่ยงความซับซ้อนของการอัปเดตลำดับชั้นการแสดงผลแบบเก็บสถานะด้วยตนเอง Compose เป็นเฟรมเวิร์ก UI แบบประกาศ

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

ตัวอย่างฟังก์ชันที่ประกอบกันได้

เมื่อใช้ Compose คุณจะสร้างอินเทอร์เฟซผู้ใช้ได้โดยกำหนดชุดฟังก์ชันที่ประกอบกันได้ซึ่งรับข้อมูลและแสดงองค์ประกอบ UI ตัวอย่างเช่น วิดเจ็ต Greeting ซึ่งรับ String และแสดงวิดเจ็ต Text ที่แสดงข้อความทักทาย

โทรศัพท์แสดงข้อความ Hello World และโค้ดสำหรับฟังก์ชันที่ประกอบกันได้ซึ่งสร้าง UI นั้น
รูปที่ 1 ฟังก์ชันที่ประกอบกันได้ซึ่งรับข้อมูลและใช้ข้อมูลนั้นเพื่อแสดงผลวิดเจ็ตข้อความบนหน้าจอ

สิ่งที่ควรทราบเกี่ยวกับฟังก์ชันนี้มีดังนี้

  • คำอธิบายประกอบ: ฟังก์ชันจะมีคำอธิบายประกอบ @Composable ฟังก์ชันที่ประกอบกันได้ทั้งหมดต้องมีคำอธิบายประกอบนี้ คำอธิบายประกอบนี้จะแจ้งให้คอมไพเลอร์ Compose ทราบว่าฟังก์ชันนี้มีไว้เพื่อแปลงข้อมูลเป็น UI
  • อินพุตข้อมูล: ฟังก์ชันจะรับข้อมูล ฟังก์ชันที่ประกอบกันได้สามารถรับพารามิเตอร์ ซึ่งช่วยให้ตรรกะของแอปอธิบาย UI ได้ ในกรณีนี้ วิดเจ็ตของเราจะยอมรับ String เพื่อให้สามารถทักทายผู้ใช้ด้วยชื่อได้
  • การแสดงผล UI: ฟังก์ชันจะแสดงข้อความใน UI โดยจะเรียกใช้ฟังก์ชันที่ประกอบกันได้ที่ชื่อ Text() ซึ่งจะสร้างองค์ประกอบ UI ของข้อความขึ้นมาจริงๆ ฟังก์ชันที่ประกอบกันได้จะแสดงลำดับชั้นของ UI โดยการเรียกใช้ฟังก์ชันที่ประกอบกันได้อื่นๆ
  • ไม่มีค่าที่แสดงผล: ฟังก์ชันจะไม่แสดงผลใดๆ ฟังก์ชัน Compose ที่แสดง UI ไม่จำเป็นต้องแสดงผลใดๆ เนื่องจากฟังก์ชันเหล่านี้อธิบายสถานะหน้าจอเป้าหมายแทนการสร้างวิดเจ็ต UI
  • คุณสมบัติ: ฟังก์ชันนี้ทำงานรวดเร็ว ไม่เปลี่ยนแปลงผลลัพธ์ และไม่มีผลข้างเคียง

    • ฟังก์ชันจะทำงานเหมือนเดิมเมื่อเรียกใช้หลายครั้งด้วยอาร์กิวเมนต์เดียวกัน และจะไม่ใช้ค่าอื่นๆ เช่น ตัวแปรส่วนกลางหรือการเรียกใช้ random()
    • ฟังก์ชันนี้จะอธิบาย UI โดยไม่มีผลข้างเคียง เช่น การแก้ไขพร็อพเพอร์ตี้หรือตัวแปรส่วนกลาง

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

การเปลี่ยนกระบวนทัศน์แบบประกาศ

เมื่อใช้ชุดเครื่องมือ UI แบบเชิงวัตถุที่จำเป็นจำนวนมาก คุณจะเริ่มต้น UI โดยสร้างอินสแตนซ์ของต้นไม้วิดเจ็ต ซึ่งมักจะทำได้โดยการขยายไฟล์เลย์เอาต์ XML วิดเจ็ตแต่ละรายการจะรักษาสถานะภายในของตนเอง และแสดงเมธอด Getter และ Setter ที่ช่วยให้ตรรกะของแอปโต้ตอบกับวิดเจ็ตได้

ในแนวทางการประกาศของ Compose วิดเจ็ตจะไม่มีสถานะและไม่แสดงฟังก์ชัน Setter หรือ Getter ที่จริงแล้ว วิดเจ็ตจะไม่แสดงเป็นออบเจ็กต์ คุณอัปเดต UI โดยเรียกใช้ฟังก์ชันที่ประกอบกันได้เดียวกันโดยมีอาร์กิวเมนต์ต่างกัน ซึ่งจะช่วยลดความซับซ้อนในการระบุสถานะให้กับรูปแบบสถาปัตยกรรม เช่น ViewModel ตามที่อธิบายไว้ในคู่มือสถาปัตยกรรมแอป จากนั้น Composable จะมีหน้าที่เปลี่ยนสถานะปัจจุบันของแอปพลิเคชันเป็น UI ทุกครั้งที่ข้อมูลที่สังเกตได้อัปเดต

ภาพแสดงขั้นตอนการไหลของข้อมูลใน Compose UI จากออบเจ็กต์ระดับสูงลงมายังออบเจ็กต์ย่อย
รูปที่ 2 ตรรกะของแอปจะให้ข้อมูลแก่ฟังก์ชันที่ประกอบกันได้ระดับบนสุด ฟังก์ชันดังกล่าวจะใช้ข้อมูลเพื่ออธิบาย UI โดยเรียกใช้ Composable อื่นๆ และส่งข้อมูลที่เหมาะสมไปยัง Composable เหล่านั้น และส่งต่อลงไปตามลำดับชั้น

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

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

เนื้อหาแบบไดนามิก

เนื่องจากฟังก์ชันที่ประกอบกันได้เขียนขึ้นใน Kotlin แทน XML จึงสามารถเป็นแบบไดนามิกได้เหมือนกับโค้ด Kotlin อื่นๆ ตัวอย่างเช่น สมมติว่าคุณต้องการสร้าง UI ที่ทักทายรายชื่อผู้ใช้

@Composable
fun Greeting(names: List<String>) {
    for (name in names) {
        Text("Hello $name")
    }
}

ฟังก์ชันนี้จะรับรายชื่อและสร้างคำทักทายสำหรับผู้ใช้แต่ละราย ฟังก์ชันที่ประกอบกันได้อาจมีความซับซ้อนมาก คุณสามารถใช้คำสั่ง if เพื่อตัดสินใจว่าต้องการแสดงองค์ประกอบ UI ที่เฉพาะเจาะจงหรือไม่ คุณสามารถใช้ลูปได้ คุณสามารถเรียกใช้ฟังก์ชันตัวช่วยได้ คุณจะมีความยืดหยุ่นอย่างเต็มที่ในภาษาที่ใช้ ประสิทธิภาพและความยืดหยุ่นนี้เป็นข้อดีหลักอย่างหนึ่งของ Jetpack Compose

การจัดองค์ประกอบใหม่

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

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

@Composable
fun ClickCounter(clicks: Int, onClick: () -> Unit) {
    Button(onClick = onClick) {
        Text("I've been clicked $clicks times")
    }
}

ทุกครั้งที่มีการคลิกปุ่ม ผู้เรียกจะอัปเดตค่าของ clicks Compose จะเรียกใช้ Lambda ด้วยฟังก์ชัน Text อีกครั้งเพื่อแสดงค่าใหม่ กระบวนการนี้เรียกว่าการจัดองค์ประกอบใหม่ ส่วนฟังก์ชันอื่นๆ ที่ไม่ได้ขึ้นอยู่กับค่าดังกล่าวจะไม่ได้รับการจัดองค์ประกอบใหม่

ดังที่ได้กล่าวไปแล้ว การจัดองค์ประกอบต้นไม้ UI ทั้งหมดใหม่อาจใช้ทรัพยากรในการประมวลผลสูง ซึ่งจะใช้พลังการประมวลผลและอายุการใช้งานแบตเตอรี่ Compose แก้ปัญหานี้ด้วยการจัดองค์ประกอบใหม่อัจฉริยะ

การจัดองค์ประกอบใหม่คือกระบวนการเรียกใช้ฟังก์ชันที่ประกอบกันได้อีกครั้งเมื่ออินพุตมีการเปลี่ยนแปลง เมื่อ Compose จัดองค์ประกอบใหม่ตามอินพุตใหม่ ระบบจะเรียกใช้เฉพาะฟังก์ชันหรือ Lambda ที่อาจมีการเปลี่ยนแปลง และข้ามส่วนที่เหลือ Compose จะจัดองค์ประกอบใหม่ได้อย่างมีประสิทธิภาพโดยข้ามฟังก์ชันหรือ Lambda ที่มีพารามิเตอร์ที่ไม่มีการเปลี่ยนแปลง

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

  • การเขียนไปยังพร็อพเพอร์ตี้ของออบเจ็กต์ที่แชร์
  • กำลังอัปเดตออบเจ็กต์ที่สังเกตได้ใน ViewModel
  • กำลังอัปเดตค่ากำหนดที่ใช้ร่วมกัน

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

ตัวอย่างเช่น โค้ดนี้จะสร้าง Composable เพื่ออัปเดตค่าใน SharedPreferences Composable ไม่ควรอ่านหรือเขียนจากค่ากำหนดที่ใช้ร่วมกันเอง แต่โค้ดนี้จะย้ายการอ่านและเขียนไปยัง ViewModel ใน Coroutine เบื้องหลังแทน ตรรกะของแอปจะส่งค่าปัจจุบันพร้อม Callback เพื่อทริกเกอร์การอัปเดต

@Composable
fun SharedPrefsToggle(
    text: String,
    value: Boolean,
    onValueChanged: (Boolean) -> Unit
) {
    Row {
        Text(text)
        Checkbox(checked = value, onCheckedChange = onValueChanged)
    }
}

เอกสารนี้กล่าวถึงสิ่งต่างๆ ที่ควรทราบเมื่อใช้ Compose ดังนี้

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

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

การจัดองค์ประกอบใหม่จะข้ามให้มากที่สุดเท่าที่จะเป็นไปได้

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

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

/**
 * Display a list of names the user can click with a header
 */
@Composable
fun NamePicker(
    header: String,
    names: List<String>,
    onNameClicked: (String) -> Unit
) {
    Column {
        // this will recompose when [header] changes, but not when [names] changes
        Text(header, style = MaterialTheme.typography.bodyLarge)
        HorizontalDivider()

        // LazyColumn is the Compose version of a RecyclerView.
        // The lambda passed to items() is similar to a RecyclerView.ViewHolder.
        LazyColumn {
            items(names) { name ->
                // When an item's [name] updates, the adapter for that item
                // will recompose. This will not recompose when [header] changes
                NamePickerItem(name, onNameClicked)
            }
        }
    }
}

/**
 * Display a single name the user can click.
 */
@Composable
private fun NamePickerItem(name: String, onClicked: (String) -> Unit) {
    Text(name, Modifier.clickable(onClick = { onClicked(name) }))
}

ขอบเขตแต่ละรายการเหล่านี้อาจเป็นสิ่งเดียวที่ดำเนินการระหว่างการจัดองค์ประกอบใหม่ Compose อาจข้ามไปยัง Lambda Column โดยไม่เรียกใช้รายการหลักใดๆ เมื่อ header มีการเปลี่ยนแปลง และเมื่อเรียกใช้ Column Compose อาจเลือกข้ามรายการของ LazyColumn หาก names ไม่มีการเปลี่ยนแปลง

ขอย้ำอีกครั้งว่าฟังก์ชันที่ประกอบกันได้หรือแลมบ์ดาทั้งหมดควรไม่มีผลข้างเคียง เมื่อต้องการดำเนินการที่ส่งผลข้างเคียง ให้เรียกใช้จาก Callback

การจัดองค์ประกอบใหม่เป็นแบบมองในแง่ดี

การจัดองค์ประกอบใหม่จะเริ่มขึ้นเมื่อใดก็ตามที่ Compose คิดว่าพารามิเตอร์ของ Composable อาจมีการเปลี่ยนแปลง การจัดองค์ประกอบใหม่เป็นแบบมองโลกในแง่ดี ซึ่งหมายความว่า Compose คาดว่าจะจัดองค์ประกอบใหม่ให้เสร็จก่อนที่พารามิเตอร์จะเปลี่ยนแปลงอีกครั้ง หากพารามิเตอร์มีการเปลี่ยนแปลงก่อนที่การจัดองค์ประกอบใหม่จะเสร็จสิ้น Compose อาจยกเลิกการจัดองค์ประกอบใหม่และเริ่มใหม่ด้วยพารามิเตอร์ใหม่

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

ยืนยันว่าฟังก์ชันที่ประกอบกันได้และ Lambda ทั้งหมดเป็นแบบ Idempotent และไม่มีผลข้างเคียงเพื่อจัดการกับการจัดองค์ประกอบใหม่ในเชิงบวก

ฟังก์ชันที่ประกอบกันได้อาจทำงานค่อนข้างบ่อย

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

ตัวอย่างเช่น หากวิดเจ็ตพยายามอ่านการตั้งค่าอุปกรณ์ วิดเจ็ตอาจอ่านการตั้งค่าเหล่านั้นหลายร้อยครั้งต่อวินาที ซึ่งส่งผลเสียต่อประสิทธิภาพของแอป

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

ฟังก์ชันที่ประกอบกันได้อาจทำงานพร้อมกันได้

Compose สามารถเพิ่มประสิทธิภาพการจัดองค์ประกอบใหม่ได้โดยเรียกใช้ฟังก์ชันที่ประกอบกันได้พร้อมกัน ซึ่งจะช่วยให้ Compose ใช้ประโยชน์จากหลายแกน และเรียกใช้ฟังก์ชันที่ประกอบกันได้ซึ่งไม่ได้อยู่บนหน้าจอโดยมีลำดับความสำคัญต่ำกว่า

การเพิ่มประสิทธิภาพนี้หมายความว่าฟังก์ชันที่ประกอบกันได้อาจทำงานภายในพูลของชุดข้อความในเบื้องหลัง หากฟังก์ชันที่ประกอบกันได้เรียกใช้ฟังก์ชันใน ViewModel Compose อาจเรียกใช้ฟังก์ชันนั้นจากหลายชุดข้อความพร้อมกัน

ฟังก์ชันที่ประกอบกันได้ทั้งหมดไม่ควรมีผลข้างเคียง เพื่อยืนยันว่าแอปพลิเคชันทำงานอย่างถูกต้อง แต่ให้ทริกเกอร์ผลข้างเคียงจาก Callback เช่น onClick ซึ่งจะทำงานในเธรด UI เสมอ

เมื่อมีการเรียกใช้ฟังก์ชันที่ประกอบกันได้ การเรียกใช้อาจเกิดขึ้นในเทรดที่แตกต่างจากผู้เรียก ซึ่งหมายความว่าควรหลีกเลี่ยงโค้ดที่แก้ไขตัวแปรในแลมบ์ดา Composable ทั้งเนื่องจากโค้ดดังกล่าวไม่ปลอดภัยต่อเทรด และเนื่องจากเป็นผลข้างเคียงที่ไม่อนุญาตของแลมบ์ดา Composable

ต่อไปนี้เป็นตัวอย่างที่แสดง Composable ซึ่งแสดงรายการและจำนวนรายการ

@Composable
fun ListComposable(myList: List<String>) {
    Row(horizontalArrangement = Arrangement.SpaceBetween) {
        Column {
            for (item in myList) {
                Text("Item: $item")
            }
        }
        Text("Count: ${myList.size}")
    }
}

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

@Composable
fun ListWithBug(myList: List<String>) {
    var items = 0

    Row(horizontalArrangement = Arrangement.SpaceBetween) {
        Column {
            for (item in myList) {
                Card {
                    Text("Item: $item")
                    items++ // Avoid! Side-effect of the column recomposing.
                }
            }
        }
        Text("Count: $items")
    }
}

ในตัวอย่างนี้ items จะได้รับการแก้ไขเมื่อมีการจัดองค์ประกอบใหม่ทุกครั้ง ซึ่งอาจเป็นทุกเฟรมของภาพเคลื่อนไหว หรือเมื่อรายการอัปเดต ไม่ว่าในกรณีใด UI จะแสดงจำนวนที่ไม่ถูกต้อง ด้วยเหตุนี้ Compose จึงไม่รองรับการเขียนลักษณะนี้ การห้ามเขียนลักษณะดังกล่าวช่วยให้เฟรมเวิร์กเปลี่ยนชุดข้อความเพื่อเรียกใช้ Lambda ที่ประกอบกันได้

ฟังก์ชันที่ประกอบกันได้สามารถทำงานในลำดับใดก็ได้

หากดูโค้ดของฟังก์ชันที่ประกอบกันได้ คุณอาจคิดว่าโค้ดจะทำงานตามลำดับที่ปรากฏ แต่ก็ไม่เสมอไป หากฟังก์ชันที่ประกอบกันได้มีการเรียกใช้ฟังก์ชันที่ประกอบกันได้อื่นๆ ฟังก์ชันเหล่านั้นอาจทำงานในลำดับใดก็ได้ Compose มีตัวเลือกในการจดจำว่าองค์ประกอบ UI บางอย่างมีลำดับความสำคัญสูงกว่าองค์ประกอบอื่นๆ และวาดองค์ประกอบเหล่านั้นก่อน

ตัวอย่างเช่น สมมติว่าคุณมีโค้ดลักษณะนี้เพื่อวาดหน้าจอ 3 หน้าจอในเลย์เอาต์แท็บ

@Composable
fun ButtonRow() {
    MyFancyNavigation {
        StartScreen()
        MiddleScreen()
        EndScreen()
    }
}

การเรียกใช้ StartScreen, MiddleScreen และ EndScreen อาจเกิดขึ้นในลำดับใดก็ได้ ซึ่งหมายความว่าคุณไม่สามารถตั้งค่าตัวแปรร่วม (ผลข้างเคียง) ใน StartScreen() และใช้ประโยชน์จากการเปลี่ยนแปลงนั้นใน MiddleScreen() ได้ แต่ฟังก์ชันแต่ละรายการจะต้องมีข้อมูลครบถ้วนในตัวเอง

ดูข้อมูลเพิ่มเติม

ดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีคิดใน Compose และฟังก์ชันที่ประกอบกันได้ที่แหล่งข้อมูลเพิ่มเติมต่อไปนี้

วิดีโอ