บันทึกสถานะ UI ใน Compose

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

แอป Android อาจสูญเสียสถานะ UI เนื่องจากกิจกรรมหรือกระบวนการ นันทนาการ การสูญเสียสถานะนี้อาจเกิดขึ้นเนื่องจากเหตุการณ์ต่อไปนี้

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

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

ตรรกะ UI

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

ในข้อมูลโค้ดต่อไปนี้ ใช้ rememberSaveable เพื่อจัดเก็บบูลีนเดียว สถานะองค์ประกอบ UI:

@Composable
fun ChatBubble(
    message: Message
) {
    var showDetails by rememberSaveable { mutableStateOf(false) }

    ClickableText(
        text = AnnotatedString(message.content),
        onClick = { showDetails = !showDetails }
    )

    if (showDetails) {
        Text(message.timestamp)
    }
}

รูปที่ 1 บับเบิลข้อความแชทจะขยายและยุบเมื่อแตะ

showDetails เป็นตัวแปรบูลีนที่จัดเก็บไว้หากลูกโป่งแชทยุบอยู่ หรือขยายอยู่

rememberSaveable จัดเก็บสถานะองค์ประกอบ UI ใน Bundle ผ่าน กลไกสถานะอินสแตนซ์ที่บันทึกไว้

โมเดลนี้สามารถจัดเก็บประเภทพื้นฐานไว้ในแพ็กเกจโดยอัตโนมัติ หากรัฐ อยู่ในประเภทที่ไม่ใช่แบบพื้นฐาน เช่น คลาสข้อมูล กลไกการจัดเก็บแบบต่างๆ เช่น การใช้คำอธิบายประกอบ Parcelize โดยใช้ Compose API เช่น listSaver และ mapSaver หรือใช้ คลาสโปรแกรมประหยัดแบบกำหนดเองที่ขยายรันไทม์ของ Compose Saver ดูเส้นทาง ในการจัดเก็บเอกสารสถานะเพื่อเรียนรู้เพิ่มเติมเกี่ยวกับวิธีการเหล่านี้

ในข้อมูลโค้ดต่อไปนี้ การเขียนใน rememberLazyListState API จัดเก็บ LazyListState ซึ่งประกอบด้วยสถานะการเลื่อนของ LazyColumn หรือ LazyRow โดยใช้ rememberSaveable โดยใช้ LazyListState.Saver ซึ่งเป็นโหมดประหยัดที่กำหนดเองที่ จัดเก็บและคืนค่าสถานะการเลื่อน หลังจากกิจกรรมหรือดำเนินการสันทนาการ (สำหรับ ตัวอย่างเช่น หลังจากการกำหนดค่าเปลี่ยนไป เช่น เปลี่ยนการวางแนวของอุปกรณ์) สถานะการเลื่อนจะยังคงอยู่

@Composable
fun rememberLazyListState(
    initialFirstVisibleItemIndex: Int = 0,
    initialFirstVisibleItemScrollOffset: Int = 0
): LazyListState {
    return rememberSaveable(saver = LazyListState.Saver) {
        LazyListState(
            initialFirstVisibleItemIndex, initialFirstVisibleItemScrollOffset
        )
    }
}

แนวทางปฏิบัติแนะนำ

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

ในการหลีกเลี่ยงการขัดข้องประเภทนี้ คุณไม่ควรเก็บวัตถุที่ซับซ้อนขนาดใหญ่ หรือ รายการออบเจ็กต์ในกลุ่ม

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

ตัวเลือกการออกแบบเหล่านี้จะขึ้นอยู่กับ Use Case เฉพาะของแอปและลักษณะ ผู้ใช้คาดหวังให้อุปกรณ์ทำงาน

ยืนยันการกู้คืนสถานะ

คุณสามารถยืนยันว่ารัฐที่จัดเก็บไว้กับ rememberSaveable ใน องค์ประกอบการเขียนจะได้รับการคืนค่าอย่างถูกต้องเมื่อกิจกรรมหรือกระบวนการ สร้างใหม่ มี API เฉพาะที่ช่วยให้บรรลุเป้าหมายนี้ได้ เช่น StateRestorationTester ดูเอกสารการทดสอบเพื่อ ดูข้อมูลเพิ่มเติม

ตรรกะทางธุรกิจ

หากสถานะองค์ประกอบ UI ของคุณถูกรอกไปที่ ViewModel เนื่องจาก คุณสามารถใช้ API ของ ViewModel ได้ตามที่ตรรกะทางธุรกิจกำหนด

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

แต่อินสแตนซ์ ViewModel ยังคงอยู่หลังจากที่กระบวนการที่เริ่มต้นโดยระบบไปแล้ว หากต้องการให้สถานะ UI พ้นจากการเปลี่ยนแปลงนี้ ให้ใช้โมดูลสถานะที่บันทึกไว้สำหรับ ViewModel ซึ่งมี API SavedStateHandle

แนวทางปฏิบัติแนะนำ

SavedStateHandle ยังใช้กลไก Bundle ในการจัดเก็บสถานะ UI ดังนั้น คุณควรใช้เพื่อจัดเก็บสถานะองค์ประกอบ UI แบบง่ายเท่านั้น

สถานะ UI ของหน้าจอ ซึ่งเกิดขึ้นจากการใช้กฎทางธุรกิจและการเข้าถึง อื่นๆ ของแอปพลิเคชันของคุณนอกเหนือจาก UI ไม่ควรเก็บใน SavedStateHandle เนื่องจากอาจมีความซับซ้อนและขนาด คุณสามารถใช้ กลไกต่างๆ ในการจัดเก็บข้อมูลที่ซับซ้อนหรือขนาดใหญ่ เช่น ทรัพยากรถาวรในเครื่อง พื้นที่เก็บข้อมูล หลังจากสร้างกระบวนการใหม่ หน้าจอจะสร้างขึ้นใหม่ด้วย คืนค่าสถานะชั่วคราวที่เก็บไว้ใน SavedStateHandle (หากมี) และ สร้างสถานะ UI ของหน้าจออีกครั้งจากชั้นข้อมูล

SavedStateHandle API

SavedStateHandle มี API ที่แตกต่างกันสำหรับจัดเก็บสถานะองค์ประกอบ UI โดยส่วนใหญ่ สิ่งที่ควรทราบ:

เขียน State saveable()
StateFlow getStateFlow()

เขียน State

ใช้ saveable API ของ SavedStateHandle เพื่ออ่านและเขียนองค์ประกอบ UI ระบุเป็น MutableState เพื่อให้รอดพ้นจากกิจกรรม และประมวลผลสันทนาการ การตั้งค่าโค้ดขั้นต่ำ

saveable API รองรับประเภทพื้นฐานที่พร้อมใช้งานทันทีและรับ stateSaver เพื่อใช้โปรแกรมประหยัดที่กำหนดเอง เช่นเดียวกับ rememberSaveable()

ในข้อมูลโค้ดต่อไปนี้ message จะจัดเก็บประเภทอินพุตของผู้ใช้ใน TextField:

class ConversationViewModel(
    savedStateHandle: SavedStateHandle
) : ViewModel() {

    var message by savedStateHandle.saveable(stateSaver = TextFieldValue.Saver) {
        mutableStateOf(TextFieldValue(""))
    }
        private set

    fun update(newMessage: TextFieldValue) {
        message = newMessage
    }

    /*...*/
}

val viewModel = ConversationViewModel(SavedStateHandle())

@Composable
fun UserInput(/*...*/) {
    TextField(
        value = viewModel.message,
        onValueChange = { viewModel.update(it) }
    )
}

ดูเอกสารประกอบของ SavedStateHandle สำหรับข้อมูลเพิ่มเติมเกี่ยวกับ โดยใช้ saveable API

StateFlow

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

ในข้อมูลโค้ด savedFilterType เป็นตัวแปร StateFlow ที่ จัดเก็บประเภทตัวกรองที่ใช้กับรายการช่องแชทในแอปแชท ได้แก่

private const val CHANNEL_FILTER_SAVED_STATE_KEY = "ChannelFilterKey"

class ChannelViewModel(
    channelsRepository: ChannelsRepository,
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {

    private val savedFilterType: StateFlow<ChannelsFilterType> = savedStateHandle.getStateFlow(
        key = CHANNEL_FILTER_SAVED_STATE_KEY, initialValue = ChannelsFilterType.ALL_CHANNELS
    )

    private val filteredChannels: Flow<List<Channel>> =
        combine(channelsRepository.getAll(), savedFilterType) { channels, type ->
            filter(channels, type)
        }.onStart { emit(emptyList()) }

    fun setFiltering(requestType: ChannelsFilterType) {
        savedStateHandle[CHANNEL_FILTER_SAVED_STATE_KEY] = requestType
    }

    /*...*/
}

enum class ChannelsFilterType {
    ALL_CHANNELS, RECENT_CHANNELS, ARCHIVED_CHANNELS
}

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

โปรดดูSavedStateHandleเอกสารประกอบสำหรับข้อมูลเพิ่มเติมเกี่ยวกับ getStateFlow() API

สรุป

ตารางต่อไปนี้สรุป API ที่ครอบคลุมในส่วนนี้ และกรณีที่ควรใช้ เพื่อบันทึกสถานะ UI แต่ละรายการ

กิจกรรม ตรรกะ UI ตรรกะทางธุรกิจใน ViewModel
การเปลี่ยนแปลงการกำหนดค่า rememberSaveable อัตโนมัติ
การสิ้นสุดกระบวนการที่เริ่มต้นโดยระบบ rememberSaveable SavedStateHandle

API ที่จะใช้จะขึ้นอยู่กับตำแหน่งของสถานะและตรรกะที่สถานะดังกล่าว ต้องการ สำหรับสถานะที่ใช้ในตรรกะ UI ให้ใช้ rememberSaveable สำหรับ ที่ใช้ในตรรกะทางธุรกิจหากคุณมีอยู่ในViewModel บันทึกโดยใช้ SavedStateHandle

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

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับวิธีการต่างๆ ในการบันทึกสถานะ UI โปรดดู การบันทึกเอกสารสถานะ UI และหน้าชั้นข้อมูลของ สถาปัตยกรรม