[Android] ลองใช้งาน Paging3 Library ในการเรียกข้อมูลจาก API มาแสดงผล และทำ Load More แบบอัตโนมัติ
แอปส่วนใหญ่ในปัจจุบันจะต้องมีการเรียกข้อมูลจาก API เพื่อมาแสดงผล และสมมุติว่าเรามีข้อมูลมากมาย คงเป็นไปไม่ได้ที่จะนำข้อมูลทั้งหมดเหล่านั้นโหลดมาแสดงผลในทีเดียว แต่จะทำการหยิบข้อมูลทีละส่วนเล็ก ๆ มาแสดงผล (small chunk) และเมื่อ user scroll ไปจนถึงข้อมูลอันสุดท้ายในลิสต์ แอปก็จะทำการ load more เพื่อเรียกข้อมูลถัดไปมาแสดงผลให้อัตโนมัติ และ google ก็ได้แนะนำ ตัว Paging3 Library เพื่อมาตอบโจทย์นักพัฒนาในเรื่องนี้
ในบทความนี้จะพามาทดลองใช้งาน Paging3 Library ในการเรียกข้อมูลผ่าน Network API เพื่อแสดงผลในแอปกันครับ (ณ ปัจจุบันของบทความนี้ Paging3 Library ยังอยู่ในสถานะ Alpha 07)
Setup Paging3 Library
ก่อนอื่นทำการติดตั้ง Paging3 Library ในโปรเจกของเราใน gradle ดังนี้
ตัวอย่างแอปของเรา จะเป็นแอปทดสอบเรียกใช้งานข้อมูล API จาก Github Service โดยเราทำการพิมพ์ค้นหา repository ตามชื่อที่เราต้องการ จากนั้นแอปจะแสดงผล repository ที่ตรงกับคำค้นหานั้นมาให้
Setup API Service
สร้าง interface ที่เราจะเรียก API จาก Github Service โดยตัว API รองรับ 3 paramer ดังนี้
- q คือ query คำค้นหาที่เราต้องการ
- page คือ ระบุข้อมูลที่ต้องการอยู่ใน page ที่เท่าไร
- per_page คือ ต้องการข้อมูลที่ return กลับมาต่อ page มีจำนวนเท่าไร
สร้าง PagingSource
ให้ทำการสร้าง PagingSource ที่จะเป็นตัวจัดการสำหรับโหลดข้อมูลจาก API ให้เรา โดยจะทำการโหลดข้อมูลทีละส่วนเล็ก ๆ ไปแสดงผลในลิสต์ ตัวอย่างโค้ด ดังนี้
จากโค้ดจะทำการเรียกข้อมูล API จาก Github Service โดยเราส่ง query คำค้นที่ต้องการ พร้อมกับระบุ position และจำนวน size ข้อมูลที่ต้องการ เมื่อได้ response กลับมา ก็ทำการ return เป็น LoadResult.Page
รูปแบบการทำงานใน function load อธิบายคร่าว ๆ ได้ ดังนี้
1. กำหนด position หากเรียกครั้งแรก params.key จะเป็น null ให้ใช้ตำแหน่ง default เป็น 1 และเมื่อมีการเรียก function load ครั้งถัดไป params.key จะนำค่าจาก nextKey มาใช้ให้อัตโนมัติ
2. ทำการเรียก API เมื่อได้ response กลับมาเป็น list ของข้อมูล จากนั้นทำการสร้าง instance เพื่อ return ค่าLoadResult.Page
กลับไป พร้อมกับระบุ argument 3 อย่างคือ data, prevKey, nextKey
- data คือ เป็นค่า response ที่ได้จากการเรียก API สมมุติเรากำหนด dataSource ว่าให้ extend<Int, Repo> ตัว data ที่เราต้องส่งกลับไปค่านี้จะต้องเป็น list ของ Repo (response: List<Repo>)
- prevKey คือ ระบุว่า key ที่แล้วคืออะไร หากโหลดครั้งแรก prevKey จะเป็น null และหากโหลดครั้งถัดไป prevKey จะเท่ากับ prevKey = position - 1
- nextKey คือ ระบุว่า key ถัดไปที่จะใช้ในการเรียก API คืออะไร โค้ดตรงนี้จะเช็ก ว่าหากข้อมูลที่ได้ response กลับมาเป็น null คือ ไม่มีข้อมูลแล้ว ให้กำหนด nextKey เป็น null ตัว data source จะรู้อัตโนมัติว่าไม่มีข้อมูลต้องโหลดต่อแล้ว และหากมีข้อมูลอีกก็ทำการ เพิ่มค่าให้ nextKey = position +1
3. ในขั้นตอนสุดท้าย หากมี error ให้ทำการสร้าง instance เพื่อ returnLoadResult.Error
กลับไปพร้อมกับ exception
ตัว LoadResult
เป็น sealed class ที่สามารถ wrap ข้อมูลส่งกลับไปในรูปแบบ ดังนี้
- ถ้าเรียกใช้งาน API สำเร็จ จะทำการ return
LoadResult.page
กลับไปในfunction load()
คือ เป็นตัวบอกให้รู้ว่าข้อมูล data ในลิสต์ คืออะไร แล้วมี nextKey สำหรับดึงข้อมูลถัดไป และ prevKey ของข้อมูลที่แล้ว คือ อะไรนั่นเอง - ถ้ามี errror เกิดขึ้นเมื่อเรียก API ให้ return
LoadResult.Error
กลับไปในfunction load()
ขั้นตอนถัดไปเราจะเรียกใช้ GithubPagingSource
เพื่อนำข้อมูลที่ได้ไปแสดงผลในแอปของเรา
สร้าง GithubRepository
หลังจากเราสร้าง GithubPagingSource ซึ่งเป็น data source สำหรับใช้ใน paging เรียบร้อยแล้ว ต่อไปทำการสร้าง GithubRepository โดยมีรูปแบบโค้ด ดังนี้
- สร้าง reference ของ GithubService ที่ทำหน้าที่โหลดข้อมูลจาก API เพื่อส่งไปให้ GithubPagingSource
- ทำการ return instance ของ Class
Pager
ที่ทำหน้าที่โหลด stream ของข้อมูลจาก data sourceในบทความนี้ใช้ Flow จาก Kotlin coroutines (Paing รองรับการสร้าง Pager ในรูปแบบ Flow, LiveData, RXJava) - ทำการสร้าง Pager โดยส่งค่า 2 Parameter ดังนี้
config
ทำการสร้าง ClassPagingConfig
ระบุจำนวนของpageSize
ที่ต้องการ และplablePlaceHolders
ว่าต้องการจะแสดง place holder หรือไม่ ในบทความนี้ไม่แสดง เลือกกำหนดเป็น falsepagingSourceRepository
โดยเป็น lambda function ที่ return ค่าเป็นpagingSource
ในกรณีนี้ เราทำการสร้าง classGithubPagingSource
ที่ส่ง paramer คือ githubService และ query เข้าไป
สร้าง UseCase สำหรับเรียกข้อมูลจาก repository
ขั้นตอนต่อไปทำการสร้าง class GetGithubPagingourceUseCase
เพื่อที่จะไปเรียกข้อมูลจาก GithubRepository และต่อไปใน view model จะทำการเรียก usecase นี้ เพื่อดึงข้อมูลจาก gitgub API มาแสดง
สร้าง View Model เพื่อส่งข้อมูลไปแสดงผลใน UI
ทำการสร้าง class MainViewModel
เมื่อ user ป้อนคำค้นหา repository ที่ต้องการ จะไปเรียก function searchRepos(queryString: String) ที่ function นี้ก็จะไปเรียก GetGithubPagingourceUseCase
โดยส่งคำค้นหา (query) เข้าไป และทำการตรวจสอบว่าข้อความที่ค้นหา เป็นข้อความเดิมหรือไม่ และเมื่อเป็นคำค้นใหม่ก็จะไปเรียก usecase และส่งข้อมูลกลับไปให้ UI แสดงผลในรูปแบบ Flow ของ PagingData
ใช้งาน PagingData Adapter
สร้าง adapter ที่ extend PagingDataAdapter<model,ViewHolder>
ที่ค่าแรกจะเป็น model ที่เราจะใช้ ซึ่งเป็น model ตัวเดียวกันที่เราสร้างไว้ใน GithubPagingSource แล้ว ใน parameter ตัวที่สองก็ คือ ViewHolder ของ RecyclerView.Adapter
ต่อไปสร้าง diffUtil
กำหนด logic ที่จะใช้ในการอัพเดตข้อมูลในลิสต์ ตรงนี้จะทำหน้าที่ตรวจสอบว่ามีข้อมูลใหม่เพิ่มเข้ามา หรือลบออกไป ก็จะไปเรียก notifyItemChanged
และnotifyItemInserted
ใน adapter ทำให้ข้อมูลที่แสดงผลในลิสต์อัพเดตตามให้เราอัตโนมัติ โดยที่เราไม่ต้องไปเขียน logic เพื่อเช็กเอง
แสดงผลข้อมูลใน UI
ขั้นตอนสุดท้าย คือ การแสดงผล ทำการ setup view สร้าง adapter ผูกกับ recyclerView ให้เรียบร้อย และส่งค่า query จาก editText ไปทำงานที่ function search() ตรงนี้จะไปเรียก function searchRepos() ของ view model อีกที ที่มีการ return ค่าเป็น Flow
จากนั้นก็ใช้ collectLatest
เพื่อรอรับค่า เมื่อได้ result กลับมา ก็ทำการอัพเดต adapter ด้วยการเรียก submitData
จากนั้นก็เป็นอันเรียบร้อย ทดสอบรันดูการแสดงผลได้ครับ
สรุป
สำหรับบทความนี้ก็เป็นตัวอย่างเบื้องต้นแอปที่ทดลองใช้งานเบื้องต้นของ Paging3 Library ในการไปเรียกข้อมูลจาก API เพื่อมาแสดงผล และทำ load more ให้แบบอัตโนมัติ แต่ยังมีอีกหลายอย่างที่บทความนี้ยังไม่ครอบคลุม เช่น การดึงข้อมูลจาก API มาเก็บใน Room Database โดยใช้ remoteMediator หรือการเพิ่ม loading ในการแสดงผลตอน load more เป็นต้น ซึ่งหากสนใจเพิ่มเติมสามารถศึกษาได้จากที่มา หรือ google codelab ของ paging library ได้เลยครับ