อายุการใช้งานของสถานะใน Compose

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

แม้ว่า remember จะทำหน้าที่เป็นเครื่องมือในการเก็บค่าไว้ระหว่างการประกอบใหม่ แต่สถานะมักจะต้องมีอยู่ต่อไปหลังจากที่การประกอบเสร็จสิ้นแล้ว หน้านี้จะอธิบายความ แตกต่างระหว่าง API remember, retain, rememberSaveable, และ rememberSerializable รวมถึงกรณีที่ควรเลือกใช้ API ใด และแนวทางปฏิบัติ แนะนำสำหรับการจัดการค่าที่เก็บไว้และค่าที่คงไว้ใน Compose

เลือกระยะเวลาที่ถูกต้อง

ใน Compose มีฟังก์ชันหลายอย่างที่คุณใช้เพื่อเก็บสถานะไว้ระหว่างการประกอบและหลังจากนั้นได้ ได้แก่ remember, retain, rememberSaveable และ rememberSerializable ฟังก์ชันเหล่านี้มีระยะเวลาและซีแมนติกที่แตกต่างกัน และเหมาะสำหรับการจัดเก็บสถานะบางประเภท ความแตกต่างมีดังที่สรุปไว้ในตารางต่อไปนี้

remember

retain

rememberSaveable, rememberSerializable

ค่าจะยังคงอยู่หลังจากการประกอบใหม่หรือไม่

ค่าจะยังคงอยู่หลังจากการสร้างกิจกรรมขึ้นมาใหม่หรือไม่

ระบบจะแสดงผลอินสแตนซ์เดียวกัน (===) เสมอ

ระบบจะแสดงผลออบเจ็กต์ที่เทียบเท่ากัน (==) ซึ่งอาจเป็นสำเนาที่ยกเลิกการซีเรียลไลซ์แล้ว

ค่าจะยังคงอยู่หลังจากการสิ้นสุดการประมวลผลหรือไม่

ประเภทข้อมูลที่รองรับ

ทั้งหมด

ต้องไม่อ้างอิงออบเจ็กต์ใดๆ ที่อาจรั่วไหลหากกิจกรรมถูกทำลาย

ต้องซีเรียลไลซ์ได้
(ด้วย Saver ที่กำหนดเองหรือด้วย kotlinx.serialization)

กรณีการใช้งาน

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

remember

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

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

เมื่อไม่มีการใช้ค่าที่เก็บไว้อีกต่อไป ระบบจะ ลืม ค่าดังกล่าวและทิ้งบันทึกของค่านั้น ระบบจะลืมค่าที่เก็บไว้เมื่อนำค่าออกจากลำดับชั้นของการประกอบ (รวมถึงเมื่อนำค่าออกแล้วเพิ่มกลับเข้าไปใหม่เพื่อย้ายไปยังตำแหน่งอื่นโดยไม่ได้ใช้ฟังก์ชันที่ประกอบกันได้ key หรือ MovableContent) หรือเมื่อเรียกใช้ด้วยพารามิเตอร์ key ที่แตกต่างกัน

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

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

อย่างไรก็ตาม คุณควรหลีกเลี่ยงกรณีต่อไปนี้

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

rememberSaveable และ rememberSerializable

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

rememberSerializable ทำงานในลักษณะเดียวกับ rememberSaveable แต่รองรับการเก็บประเภทข้อมูลที่ซับซ้อนซึ่งซีเรียลไลซ์ได้ด้วยไลบรารี kotlinx.serialization โดยอัตโนมัติ เลือก rememberSerializable หากประเภทข้อมูลของคุณมีการทำเครื่องหมายด้วย @Serializable (หรือทำเครื่องหมายได้) และเลือก rememberSaveable ในกรณีอื่นๆ

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

โปรดทราบว่า rememberSaveable และ rememberSerializable จะบันทึกค่าที่จดจำไว้โดยการซีเรียลไลซ์ค่าเหล่านั้นลงใน Bundle ซึ่งมีผล 2 ประการดังนี้

  • ค่าที่คุณจดจำไว้ต้องแสดงด้วยประเภทข้อมูลต่อไปนี้อย่างน้อย 1 ประเภท ได้แก่ ประเภทข้อมูลพื้นฐาน (รวมถึง Int, Long, Float, Double), String หรืออาร์เรย์ของประเภทข้อมูลเหล่านี้
  • เมื่อมีการกู้คืนค่าที่บันทึกไว้ ค่าดังกล่าวจะเป็นอินสแตนซ์ใหม่ที่เท่ากับ (==) แต่ไม่ใช่การอ้างอิงเดียวกัน (===) กับที่การประกอบใช้ก่อนหน้านี้

หากต้องการจัดเก็บประเภทข้อมูลที่ซับซ้อนมากขึ้นโดยไม่ใช้ kotlinx.serialization คุณสามารถใช้ Saver ที่กำหนดเองเพื่อซีเรียลไลซ์และยกเลิกการซีเรียลไลซ์ออบเจ็กต์เป็นประเภทข้อมูลที่รองรับ โปรดทราบว่า Compose เข้าใจประเภทข้อมูลทั่วไป เช่น State, List, Map, Set ฯลฯ โดยค่าเริ่มต้น และจะแปลงข้อมูลเหล่านี้เป็นประเภทที่รองรับโดยอัตโนมัติ ตัวอย่างต่อไปนี้แสดง Saver สำหรับคลาส Size ซึ่งใช้ listSaver ในการแพ็กพร็อพเพอร์ตี้ทั้งหมดของ Size ลงในรายการ

data class Size(val x: Int, val y: Int) {
    object Saver : androidx.compose.runtime.saveable.Saver<Size, Any> by listSaver(
        save = { listOf(it.x, it.y) },
        restore = { Size(it[0], it[1]) }
    )
}

@Composable
fun rememberSize(x: Int, y: Int) {
    rememberSaveable(x, y, saver = Size.Saver) {
        Size(x, y)
    }
}

retain

API retain อยู่ระหว่าง remember กับ rememberSaveable/rememberSerializable ในแง่ของระยะเวลาที่จดจำ ค่า โดยมีชื่อแตกต่างกันเนื่องจากค่าที่คงไว้มีวงจรชีวิตที่แตกต่างจากค่าที่เก็บไว้

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

retain สามารถเก็บค่าที่ซีเรียลไลซ์ไม่ได้ เช่น นิพจน์แลมบ์ดา โฟลว์ และออบเจ็กต์ขนาดใหญ่ เช่น บิตแมป ไว้ได้ โดยแลกกับการมีวงจรชีวิตที่สั้นกว่า rememberSaveable ตัวอย่างเช่น คุณสามารถใช้ retain เพื่อจัดการเครื่องเล่นสื่อ (เช่น ExoPlayer) เพื่อป้องกันไม่ให้การเล่นสื่อหยุดชะงักระหว่างการเปลี่ยนแปลงการกำหนดค่า

@Composable
fun MediaPlayer() {
    // Use the application context to avoid a memory leak
    val applicationContext = LocalContext.current.applicationContext
    val exoPlayer = retain { ExoPlayer.Builder(applicationContext).apply { /* ... */ }.build() }
    // ...
}

retain กับ ViewModel

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

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

ViewModel ยังมีการผสานรวมแบบพร้อมใช้งานสำหรับการแทรกทรัพยากร Dependency กับ Dagger และ Hilt, การผสานรวมกับ SavedState และการรองรับโครูทีนในตัวสำหรับการเปิดใช้งานงานเบื้องหลัง จึงทำให้ ViewModel เป็นตำแหน่งที่เหมาะสำหรับการเปิดใช้งานงานเบื้องหลังและคำขอเครือข่าย การโต้ตอบกับแหล่งข้อมูลอื่นๆ ในโปรเจ็กต์ และเลือกที่จะบันทึกและเก็บสถานะ UI ที่สำคัญซึ่งควรคงไว้เมื่อมีการเปลี่ยนแปลงการกำหนดค่าใน ViewModel และยังคงอยู่ได้เมื่อมีการสิ้นสุดการประมวลผล

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

สำหรับผู้ใช้ขั้นสูงที่ออกแบบรูปแบบสถาปัตยกรรมแอปที่กำหนดเองนอกเหนือจากคำแนะนำสถาปัตยกรรมแอป Android สมัยใหม่ retain ยังใช้เพื่อสร้าง API "คล้าย ViewModel" ภายในองค์กรได้ด้วย แม้ว่าจะไม่มีการรองรับโครูทีนและสถานะที่บันทึกไว้แบบพร้อมใช้งาน แต่ retain สามารถทำหน้าที่เป็นองค์ประกอบพื้นฐานสำหรับวงจรชีวิตของ ViewModel ที่คล้ายกันโดยมีฟีเจอร์เหล่านี้สร้างขึ้นอยู่ด้านบน รายละเอียดเฉพาะของวิธีออกแบบคอมโพเนนต์ดังกล่าวอยู่นอกขอบเขตของคู่มือนี้

retain

ViewModel

การกำหนดขอบเขต

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

ViewModel เป็นซิงเกิลตันภายใน ViewModelStore

การทำลาย

เมื่อออกจากลำดับชั้นของการประกอบอย่างถาวร

เมื่อ ViewModelStore ถูกล้างหรือทำลาย

ฟังก์ชันการทำงานเพิ่มเติม

รับการเรียกกลับได้เมื่อออบเจ็กต์อยู่ในลำดับชั้นของการประกอบ หรือไม่

coroutineScope ในตัว, การรองรับ SavedStateHandle, แทรกได้โดยใช้ Hilt

เป็นของ

RetainedValuesStore

ViewModelStore

กรณีการใช้งาน

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

รวม retain กับ rememberSaveable หรือ rememberSerializable

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

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

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

ในกรณีทั้งหมดนี้ เราขอแนะนำให้แยกคลาสออกเป็น 3 ส่วน ได้แก่ ข้อมูลที่บันทึกไว้ ข้อมูลที่คงไว้ และออบเจ็กต์ "ตัวกลาง" ที่ไม่มีสถานะเป็นของตัวเองและมอบหมายให้ออบเจ็กต์ที่คงไว้และออบเจ็กต์ที่บันทึกไว้เพื่ออัปเดตสถานะตามความเหมาะสม รูปแบบนี้มีลักษณะดังนี้

@Composable
fun rememberAndRetain(): CombinedRememberRetained {
    val saveData = rememberSerializable(serializer = serializer<ExtractedSaveData>()) {
        ExtractedSaveData()
    }
    val retainData = retain { ExtractedRetainData() }
    return remember(saveData, retainData) {
        CombinedRememberRetained(saveData, retainData)
    }
}

@Serializable
data class ExtractedSaveData(
    // All values that should persist process death should be managed by this class.
    var savedData: AnotherSerializableType = defaultValue()
)

class ExtractedRetainData {
    // All values that should be retained should appear in this class.
    // It's possible to manage a CoroutineScope using RetainObserver.
    // See the full sample for details.
    var retainedData = Any()
}

class CombinedRememberRetained(
    private val saveData: ExtractedSaveData,
    private val retainData: ExtractedRetainData,
) {
    fun doAction() {
        // Manipulate the retained and saved state as needed.
    }
}

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

ดูตัวอย่างฉบับเต็ม (RetainAndSaveSample.kt) เพื่อดูตัวอย่างที่สมบูรณ์ของ วิธีใช้รูปแบบนี้

การจดจำตามตำแหน่งและเลย์เอาต์แบบปรับขนาดได้

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

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

สำหรับคอมโพเนนต์แบบพร้อมใช้งาน เช่น ListDetailPaneScaffold และ NavDisplay (จาก Jetpack Navigation 3) จะไม่มีปัญหาและสถานะจะยังคงอยู่ตลอดการเปลี่ยนแปลงเลย์เอาต์ สำหรับคอมโพเนนต์ที่กำหนดเองซึ่งปรับให้เข้ากับฟอร์มแฟกเตอร์ ให้ตรวจสอบว่าการเปลี่ยนแปลงเลย์เอาต์ไม่ส่งผลต่อสถานะโดยทำอย่างใดอย่างหนึ่งต่อไปนี้

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

ฟังก์ชันโรงงาน `remember`

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

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

  • ใส่คำนำหน้า remember ในชื่อฟังก์ชัน หรือหากการใช้งานฟังก์ชัน ขึ้นอยู่กับออบเจ็กต์ที่ retained และ API จะไม่ พัฒนาไปใช้ remember รูปแบบอื่น ให้ใช้คำนำหน้า retain แทน
  • ใช้ rememberSaveable หรือ rememberSerializable หากเลือกการเก็บสถานะไว้และสามารถเขียนการใช้งาน Saver ที่ถูกต้องได้
  • หลีกเลี่ยงผลข้างเคียงหรือการเริ่มต้นค่าตาม CompositionLocal ที่อาจไม่เกี่ยวข้องกับการใช้งาน โปรดทราบว่าตำแหน่งที่สร้างสถานะอาจไม่ใช่ตำแหน่งที่ใช้สถานะ

@Composable
fun rememberImageState(
    imageUri: String,
    initialZoom: Float = 1f,
    initialPanX: Int = 0,
    initialPanY: Int = 0
): ImageState {
    return rememberSaveable(imageUri, saver = ImageState.Saver) {
        ImageState(
            imageUri, initialZoom, initialPanX, initialPanY
        )
    }
}

data class ImageState(
    val imageUri: String,
    val zoom: Float,
    val panX: Int,
    val panY: Int
) {
    object Saver : androidx.compose.runtime.saveable.Saver<ImageState, Any> by listSaver(
        save = { listOf(it.imageUri, it.zoom, it.panX, it.panY) },
        restore = { ImageState(it[0] as String, it[1] as Float, it[2] as Int, it[3] as Int) }
    )
}