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

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

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

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

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

ตรรกะ UI

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

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

ในข้อมูลโค้ดต่อไปนี้ rememberLazyListState Compose 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 อื่นๆ ที่เขียนลงในสถานะ UI ด้วย เช่น การเรียก onSaveInstanceState() ใน กิจกรรมของคุณ อย่างไรก็ตาม ขนาดของ Bundle นี้มีจำกัด และการจัดเก็บออบเจ็กต์ขนาดใหญ่อาจทำให้เกิดข้อยกเว้น TransactionTooLarge ในรันไทม์ ซึ่งอาจเป็นปัญหาอย่างยิ่งในแอป Activity แบบหน้าเดียวที่ใช้ Bundle เดียวกันในทั้งแอป

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

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

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

ยืนยันการคืนค่าสถานะ

คุณสามารถยืนยันได้ว่าระบบจะกู้คืนสถานะที่จัดเก็บไว้ด้วย rememberSaveable ในองค์ประกอบ Compose อย่างถูกต้องเมื่อสร้างกิจกรรมหรือกระบวนการขึ้นมาใหม่ มี 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) }
    )
}

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

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 ติดตามโฟลว์เพื่อ กรองช่อง

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

สรุป

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

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

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

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

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