ใน Jetpack Compose ฟังก์ชันที่ประกอบกันได้มักจะเก็บสถานะโดยใช้ฟังก์ชัน remember โดยค่าที่เก็บไว้สามารถนำกลับมาใช้ใหม่ได้ในการประกอบใหม่ ตามที่
อธิบายไว้ใน สถานะและ Jetpack Compose
แม้ว่า remember จะทำหน้าที่เป็นเครื่องมือในการเก็บค่าไว้ระหว่างการประกอบใหม่ แต่สถานะมักจะต้องมีอยู่ต่อไปหลังจากที่การประกอบเสร็จสิ้นแล้ว หน้านี้จะอธิบายความ
แตกต่างระหว่าง API remember, retain, rememberSaveable,
และ rememberSerializable รวมถึงกรณีที่ควรเลือกใช้ API ใด และแนวทางปฏิบัติ
แนะนำสำหรับการจัดการค่าที่เก็บไว้และค่าที่คงไว้ใน Compose
เลือกระยะเวลาที่ถูกต้อง
ใน Compose มีฟังก์ชันหลายอย่างที่คุณใช้เพื่อเก็บสถานะไว้ระหว่างการประกอบและหลังจากนั้นได้ ได้แก่ remember, retain, rememberSaveable และ
rememberSerializable ฟังก์ชันเหล่านี้มีระยะเวลาและซีแมนติกที่แตกต่างกัน และเหมาะสำหรับการจัดเก็บสถานะบางประเภท ความแตกต่างมีดังที่สรุปไว้ในตารางต่อไปนี้
|
|
|
|
|---|---|---|---|
ค่าจะยังคงอยู่หลังจากการประกอบใหม่หรือไม่ |
✅ |
✅ |
✅ |
ค่าจะยังคงอยู่หลังจากการสร้างกิจกรรมขึ้นมาใหม่หรือไม่ |
❌ |
✅ ระบบจะแสดงผลอินสแตนซ์เดียวกัน ( |
✅ ระบบจะแสดงผลออบเจ็กต์ที่เทียบเท่ากัน ( |
ค่าจะยังคงอยู่หลังจากการสิ้นสุดการประมวลผลหรือไม่ |
❌ |
❌ |
✅ |
ประเภทข้อมูลที่รองรับ |
ทั้งหมด |
ต้องไม่อ้างอิงออบเจ็กต์ใดๆ ที่อาจรั่วไหลหากกิจกรรมถูกทำลาย |
ต้องซีเรียลไลซ์ได้ |
กรณีการใช้งาน |
|
|
|
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 กับ 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) } ) }