อายุการใช้งานของสถานะใน 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)

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

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

remember

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

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

เมื่อไม่ได้ใช้ค่าที่จดจำไว้อีกต่อไป ระบบจะลืมค่าดังกล่าวและทิ้งบันทึกของค่านั้น ระบบจะลืมค่าที่จดจำไว้เมื่อนำค่าออกจาก ลำดับชั้นการเขียน (รวมถึงเมื่อนำค่าออกและเพิ่มกลับเพื่อย้ายไปยัง ตำแหน่งอื่นโดยไม่ต้องใช้ key composable หรือ 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 ประการ ดังนี้

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

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

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

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

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

เพื่อแลกกับวงจร rememberSaveable ที่สั้นกว่านี้ Retain จึงสามารถ เก็บค่าที่ทำการซีเรียลไลซ์ไม่ได้ เช่น นิพจน์ Lambda, Flow และ ออบเจ็กต์ขนาดใหญ่ เช่น บิตแมป ตัวอย่างเช่น คุณสามารถใช้ 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 ยังรวมถึงการผสานรวมที่พร้อมใช้งานสำหรับการแทรกการอ้างอิง ด้วย Dagger และ Hilt, การผสานรวมกับ SavedState และการรองรับโครูทีนในตัว สำหรับการเปิดใช้งานงานในเบื้องหลัง ซึ่งทำให้ ViewModel เป็นที่ที่เหมาะสำหรับ เปิดใช้งานงานเบื้องหลังและคำขอเครือข่าย โต้ตอบกับแหล่งข้อมูลอื่นๆ ในโปรเจ็กต์ และบันทึกและคงสถานะ UI ที่สำคัญต่อภารกิจ (ไม่บังคับ) ซึ่งควรคงไว้เมื่อมีการเปลี่ยนแปลงการกำหนดค่าใน ViewModel และ ยังคงอยู่แม้ว่ากระบวนการจะสิ้นสุดลง

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

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

retain

ViewModel

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

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

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

การทำลาย

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

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

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

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

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

เป็นของ

RetainedValuesStore

ViewModelStore

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

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

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

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

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

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

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

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

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

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

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

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

จดจำฟังก์ชันจากโรงงาน

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