เมื่อทำงานกับข้อมูลที่แบ่งหน้า คุณมักจะต้อง เปลี่ยนรูปแบบสตรีมข้อมูลขณะโหลด เช่น คุณอาจต้องกรองรายการ หรือแปลงรายการเป็นประเภทอื่นก่อนที่จะแสดงใน UI กรณีการใช้งานทั่วไปอีกอย่างหนึ่งสำหรับการเปลี่ยนรูปแบบสตรีมข้อมูลคือการเพิ่มตัวคั่นรายการ
โดยทั่วไปแล้ว การใช้การเปลี่ยนรูปแบบกับสตรีมข้อมูลโดยตรงจะช่วยให้คุณ แยกโครงสร้างที่เก็บและโครงสร้าง UI ออกจากกันได้
หน้านี้ถือว่าคุณคุ้นเคยกับการใช้งานพื้นฐานของไลบรารีการแบ่งหน้าอยู่แล้ว
ใช้การเปลี่ยนรูปแบบพื้นฐาน
เนื่องจาก PagingData อยู่ในสตรีมแบบรีแอกทีฟ คุณจึงใช้การดำเนินการเปลี่ยนรูปแบบกับข้อมูลได้ทีละรายการระหว่างการโหลดข้อมูลและการนำเสนอ
หากต้องการใช้การเปลี่ยนรูปแบบกับออบเจ็กต์ PagingData แต่ละรายการในสตรีม
ให้วางการเปลี่ยนรูปแบบไว้ภายใน
map()
การดำเนินการในสตรีม
pager.flow // Type is Flow<PagingData<User>>. // Map the outer stream so that the transformations are applied to // each new generation of PagingData. .map { pagingData -> // Transformations in this block are applied to the items // in the paged data. }
แปลงข้อมูล
การดำเนินการขั้นพื้นฐานที่สุดในสตรีมข้อมูลคือการแปลงข้อมูลเป็นประเภทอื่น
เมื่อมีสิทธิ์เข้าถึงออบเจ็กต์ PagingData แล้ว คุณจะดำเนินการ map()
กับแต่ละรายการในรายการที่แบ่งหน้าภายในออบเจ็กต์ PagingData
ได้
กรณีการใช้งานทั่วไปอย่างหนึ่งคือการแมปออบเจ็กต์เลเยอร์เครือข่ายหรือฐานข้อมูลกับออบเจ็กต์ที่ใช้ในเลเยอร์ UI โดยเฉพาะ ตัวอย่างด้านล่างแสดงวิธี ใช้การดำเนินการแมปประเภทนี้
pager.flow // Type is Flow<PagingData<User>>. .map { pagingData -> pagingData.map { user -> UiModel(user) } }
การแปลงข้อมูลที่พบบ่อยอีกอย่างคือการรับอินพุตจากผู้ใช้ เช่น สตริงการค้นหา แล้วแปลงเป็นเอาต์พุตคำขอเพื่อแสดง การตั้งค่านี้ ต้องคอยรับฟังและบันทึกข้อมูลคำค้นหาของผู้ใช้ ทำการ ขอ และส่งผลการค้นหากลับไปยัง UI
คุณสามารถฟังอินพุตการค้นหาโดยใช้ Stream API เก็บข้อมูลอ้างอิงสตรีม
ไว้ใน ViewModel เลเยอร์ UI ไม่ควรมีสิทธิ์เข้าถึงโดยตรง แต่ควร
กำหนดฟังก์ชันเพื่อแจ้ง ViewModel เกี่ยวกับคำค้นหาของผู้ใช้แทน
private val queryFlow = MutableStateFlow("") fun onQueryChanged(query: String) { queryFlow.value = query }
เมื่อค่าการค้นหาเปลี่ยนแปลงในสตรีมข้อมูล คุณสามารถดำเนินการเพื่อ แปลงค่าการค้นหาเป็นประเภทข้อมูลที่ต้องการและแสดงผลลัพธ์ไปยังเลเยอร์ UI ฟังก์ชัน Conversion ที่เฉพาะเจาะจงจะขึ้นอยู่กับภาษาและเฟรมเวิร์ก ที่ใช้ แต่ทั้งหมดจะให้ฟังก์ชันการทำงานที่คล้ายกัน
val querySearchResults: Flow<User> = queryFlow.flatMapLatest { query -> // The database query returns a Flow which is output through // querySearchResults userDatabase.searchBy(query) }
การใช้การดำเนินการอย่าง flatMapLatest หรือ switchMap จะช่วยให้มั่นใจได้ว่าระบบจะแสดงเฉพาะผลลัพธ์ล่าสุดใน UI หากผู้ใช้เปลี่ยนอินพุตคำค้นหาก่อนที่การดำเนินการฐานข้อมูลจะเสร็จสมบูรณ์ การดำเนินการเหล่านี้จะทิ้งผลลัพธ์จากคำค้นหาเก่าและเปิดการค้นหาใหม่ทันที
กรองข้อมูล
การดำเนินการอีกอย่างที่พบบ่อยคือการกรอง คุณสามารถกรองข้อมูลตามเกณฑ์ จากผู้ใช้ หรือนำข้อมูลออกจาก UI ได้หากควรซ่อนข้อมูลตาม เกณฑ์อื่นๆ
คุณต้องวางการดำเนินการตัวกรองเหล่านี้ไว้ภายในเรียกใช้ map() เนื่องจากตัวกรองใช้กับออบเจ็กต์ PagingData เมื่อกรองข้อมูลออกจาก
PagingDataแล้ว ระบบจะส่งอินสแตนซ์ PagingData ใหม่ไปยังเลเยอร์ UI เพื่อ
แสดง
pager.flow // Type is Flow<PagingData<User>>. .map { pagingData -> pagingData.filter { user -> !user.hiddenFromUi } }
เพิ่มตัวคั่นรายการ
ไลบรารีการแบ่งหน้าจะรองรับตัวคั่นรายการแบบไดนามิก คุณปรับปรุงความสามารถในการอ่านรายการได้โดยการแทรกตัวคั่นลงในสตรีมข้อมูลโดยตรงเป็น คอมโพสในเลย์เอาต์ ด้วยเหตุนี้ ตัวคั่นจึงเป็น Composable ที่มีฟีเจอร์ครบถ้วน ซึ่งช่วยให้โต้ตอบ จัดรูปแบบ และเข้าถึงความหมายของการช่วยเหลือพิเศษได้อย่างเต็มที่
ขั้นตอนในการแทรกตัวคั่นลงในรายการแบบแบ่งหน้ามี 3 ขั้นตอนดังนี้
- แปลงโมเดล UI เพื่อรองรับรายการตัวคั่น วิธีหนึ่งในการทำเช่นนี้ คือการรวมรายการข้อมูลและตัวคั่นไว้ในคลาสที่ปิดผนึกรายการเดียว ซึ่งช่วยให้ UI จัดการรายการประเภทต่างๆ ในรายการเดียวกันได้
- เปลี่ยนสตรีมข้อมูลเพื่อเพิ่มตัวคั่นระหว่างการโหลดข้อมูลและการนำเสนอข้อมูลแบบไดนามิก
- อัปเดต UI เพื่อจัดการรายการตัวคั่น
แปลงโมเดล UI
ไลบรารีการแบ่งหน้าจะแทรกตัวคั่นรายการลงใน UI เป็นรายการจริง แต่ต้องแยกรายการตัวคั่นออกจากรายการข้อมูล ในรายการเพื่อให้มั่นใจว่าระบบจะแสดงผลทั้ง 2 ประเภทที่ประกอบได้แตกต่างกัน วิธีแก้คือการสร้างคลาสที่ปิดผนึกของ Kotlin พร้อมคลาสย่อยเพื่อแสดงข้อมูลและตัวคั่น หรือคุณจะสร้างคลาสพื้นฐานที่คลาสรายการในลิสต์และคลาสตัวคั่นขยายก็ได้
สมมติว่าคุณต้องการเพิ่มตัวคั่นลงในรายการของUserรายการที่แบ่งหน้า ข้อมูลโค้ดต่อไปนี้แสดงวิธีสร้างคลาสพื้นฐานที่อินสแตนซ์อาจเป็น UserModel หรือ SeparatorModel
sealed class UiModel { class UserModel(val id: String, val label: String) : UiModel() { constructor(user: User) : this(user.id, user.label) } class SeparatorModel(val description: String) : UiModel() }
เปลี่ยนรูปแบบสตรีมข้อมูล
คุณต้องใช้การเปลี่ยนรูปแบบกับสตรีมข้อมูลหลังจากโหลดและก่อนที่จะนำเสนอ การเปลี่ยนรูปแบบควรทำสิ่งต่อไปนี้
- แปลงรายการในรายการที่โหลดแล้วเพื่อให้ตรงกับประเภทรายการพื้นฐานใหม่
- ใช้วิธี
PagingData.insertSeparators()เพื่อเพิ่มตัวคั่น
ดูข้อมูลเพิ่มเติมเกี่ยวกับการดำเนินการเปลี่ยนรูปแบบได้ที่ใช้การเปลี่ยนรูปแบบพื้นฐาน
ตัวอย่างต่อไปนี้แสดงการดำเนินการเปลี่ยนรูปแบบเพื่ออัปเดตสตรีม PagingData<User> เป็นสตรีม PagingData<UiModel> โดยเพิ่มตัวคั่น
pager.flow.map { pagingData: PagingData<User> -> // Map outer stream, so you can perform transformations on // each paging generation. pagingData .map { user -> // Convert items in stream to UiModel.UserModel. UiModel.UserModel(user) } .insertSeparators<UiModel.UserModel, UiModel> { before, after -> when { before == null -> UiModel.SeparatorModel("HEADER") after == null -> UiModel.SeparatorModel("FOOTER") shouldSeparate(before, after) -> UiModel.SeparatorModel( "BETWEEN ITEMS $before AND $after" ) // Return null to avoid adding a separator between two items. else -> null } } }
จัดการตัวคั่นใน UI
ขั้นตอนสุดท้ายคือการเปลี่ยน UI เพื่อรองรับประเภทรายการตัวคั่น
ในเลย์เอาต์แบบเลซี่ คุณสามารถจัดการรายการหลายประเภทได้โดยการตรวจสอบประเภทของ
แต่ละ UiModel ที่ปล่อยออกมา เมื่อวนซ้ำผ่านข้อมูลที่แบ่งหน้า ให้ใช้คำสั่ง when
เพื่อเรียก Composable ที่เหมาะสม ซึ่งช่วยให้คุณระบุ UI ที่แตกต่างกันสำหรับรายการข้อมูลและตัวคั่นได้
@Composable fun UserList(pagingItems: LazyPagingItems) { LazyColumn { items( count = pagingItems.itemCount, key = { index -> val item = pagingItems.peek(index) when (item) { is UiModel.UserModel -> item.user.id is UiModel.SeparatorModel -> item.description else -> index } } ) { index -> when (val item = pagingItems[index]) { is UiModel.UserModel -> UserItemComposable(item.user) is UiModel.SeparatorModel -> SeparatorComposable(item.description) null -> PlaceholderComposable() } } } }
หลีกเลี่ยงการทำงานซ้ำ
ปัญหาสำคัญที่ควรหลีกเลี่ยงคือการให้แอปทำงานที่ไม่จำเป็น การดึงข้อมูลเป็น การดำเนินการที่ใช้ทรัพยากรมาก และการเปลี่ยนรูปแบบข้อมูลก็อาจใช้เวลาอันมีค่าของคุณด้วย เมื่อโหลดและเตรียมข้อมูลเพื่อแสดงใน UI แล้ว คุณควรบันทึกข้อมูลไว้ ในกรณีที่มีการเปลี่ยนแปลงการกำหนดค่าและต้องสร้าง UI ใหม่
การดำเนินการ cachedIn() จะแคชผลลัพธ์ของการเปลี่ยนรูปแบบที่เกิดขึ้น
ก่อนการดำเนินการดังกล่าว โดยปกติ คุณจะใช้ตัวดำเนินการนี้ใน ViewModel ก่อน
แสดง Flow ต่อ Composable
หากต้องการจัดการแคชอย่างถูกต้อง ให้ส่ง CoroutineScope ไปยัง cachedIn() ดังที่แสดง
ในตัวอย่างต่อไปนี้โดยใช้ viewModelScope
pager.flow // Type is Flow<PagingData<User>>. .map { pagingData -> pagingData.filter { user -> !user.hiddenFromUi } .map { user -> UiModel.UserModel(user) } } .cachedIn(viewModelScope)
ดูข้อมูลเพิ่มเติมเกี่ยวกับการใช้ cachedIn() กับสตรีมของ PagingData ได้ที่
ตั้งค่าสตรีมของ
PagingData
แหล่งข้อมูลเพิ่มเติม
ดูข้อมูลเพิ่มเติมเกี่ยวกับไลบรารี Paging ได้ที่แหล่งข้อมูลเพิ่มเติมต่อไปนี้
เอกสาร
ดูเนื้อหา
แนะนำสำหรับคุณ
- หมายเหตุ: ข้อความลิงก์จะแสดงเมื่อ JavaScript ปิดอยู่
- โหลดและแสดงข้อมูลแบบแบ่งหน้า
- ทดสอบการใช้งานการแบ่งหน้า
- จัดการและแสดงสถานะการโหลด