การเขียน

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 โดยไม่มีผลกระทบข้างเคียง เช่น การแก้ไขพร็อพเพอร์ตี้หรือตัวแปรร่วม

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

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

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

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

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

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

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

ภาพวิธีที่องค์ประกอบ UI ตอบสนองต่อการโต้ตอบ โดยทริกเกอร์เหตุการณ์ที่ดำเนินการโดย App Logic

รูปที่ 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
  • การอัปเดตค่ากำหนดที่แชร์

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

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

@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 โดยไม่เรียกใช้แลมบ์ดาหลักเลยเมื่อ header เปลี่ยนแปลง และเมื่อเรียกใช้ Column คอมโพซอาจเลือกที่จะข้ามรายการของ LazyColumn หาก names ไม่มีการเปลี่ยนแปลง

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

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

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

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

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

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

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

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

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

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

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

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

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

วิดีโอ