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

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

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

เลือกอายุการใช้งานที่ถูกต้อง

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

remember

retain

rememberSaveable, rememberSerializable

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

✅

✅

✅

ค่าต่างๆ จะยังคงอยู่เมื่อมีการสร้างกิจกรรมใหม่หรือไม่

❌

✅

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

✅

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

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

❌

❌

✅

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

ทั้งหมด

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

ต้องทำให้เป็นอนุกรมได้
(ด้วย Saver ที่กำหนดเองหรือด้วย kotlinx.serialization)

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

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

remember

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

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

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

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

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

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

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

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

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

retain

ViewModel

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

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

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

การทำลาย

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

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

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

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

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

เป็นของ

RetainedValuesStore

ViewModelStore

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

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

รวม retain และ rememberSaveable หรือ rememberSerializable

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

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

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

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

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

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

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

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