การเขียน

Jetpack Compose เป็นชุดเครื่องมือ UI แบบประกาศสิ่งที่ต้องการที่ทันสมัยสำหรับ Android Compose ช่วยให้คุณเขียนและดูแลรักษา UI ของแอปได้ง่ายขึ้นด้วยDeclarative 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 คุณจะสร้างอินเทอร์เฟซผู้ใช้ได้โดยกำหนดชุดฟังก์ชัน composable ที่รับข้อมูลและแสดงองค์ประกอบ UI ตัวอย่างง่ายๆ คือวิดเจ็ต Greeting ซึ่งรับ String และแสดงวิดเจ็ต Text ที่แสดงข้อความทักทาย

ภาพหน้าจอของโทรศัพท์ที่แสดงข้อความ "Hello World" และโค้ดของฟังก์ชัน Composable ง่ายๆ ที่สร้าง UI ดังกล่าว

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

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

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

  • ฟังก์ชันรับข้อมูล ฟังก์ชัน Composable ยอมรับพารามิเตอร์ได้ ซึ่งช่วยให้ตรรกะของแอปอธิบาย UI ได้ ในกรณีนี้ วิดเจ็ตของเรารับ String เพื่อให้ทักทายผู้ใช้ด้วยชื่อได้

  • ฟังก์ชันจะแสดงข้อความใน UI โดยเรียกใช้ฟังก์ชัน Text() composable ซึ่งสร้างองค์ประกอบ UI ข้อความ ฟังก์ชันที่ประกอบกันได้จะแสดงลําดับชั้น UI โดยการเรียกใช้ฟังก์ชันอื่นๆ ที่ประกอบกันได้

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

  • ฟังก์ชันนี้ทำงานได้อย่างรวดเร็ว ทำงานซ้ำได้ และไม่มีผลข้างเคียง

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

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

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

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

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

ภาพแสดงการไหลของข้อมูลใน UI ของ Compose จากออบเจ็กต์ระดับสูงลงไปจนถึงออบเจ็กต์ย่อย

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

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

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

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

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

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

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

เอกสารนี้กล่าวถึงสิ่งที่ควรทราบเมื่อใช้เครื่องมือเขียน

  • การคอมโพสิชันใหม่จะข้ามฟังก์ชันและ 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) }))
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

ต่อไปนี้คือตัวอย่างการแสดงคอมโพสิเบิลที่แสดงรายการและจำนวนรายการ

@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 แบบคอมโพสิเบิลได้

ฟังก์ชันที่ประกอบกันได้จะทํางานในลําดับใดก็ได้

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

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

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

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

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

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

วิดีโอ