ทำ caching data ในแอพของเราผ่าน Room กันเถอะ

Android Jan 8, 2021

เธอเคยเห็น empty state ใน Facebook, Twitter หรืออะไรพวกนี้ไหมนะ? จริงๆเขาใช้ Room ไหมเราก็ยังไม่รู้ แต่เราจะลองใช้จะได้ caching data เหมือนเขาเนาะ

ปกติเราทำแอพเราต้องเผื่อเคสที่มันไม่ 200 OK ว่าจะให้แสดงเป็นแบบใหญ่ ส่วนใหญ่เป็น empty state เนอะ แต่ๆๆๆๆๆ ถ้าพวกแอพ social ต่างๆที่มี feed ต่างๆ เวลาเราปิดอินเตอร์เน็ตแล้วเปิดแอพใหม่ พบว่าเหมือนเรา online นะ แค่ตัว data มันเหมือนเดิมเท่านั้นเอง แล้วเขาใช้อะไรทำหล่ะ

ใน Android Architecture Components ที่อยู่ใน Android Jetpack นั้นมีอยู่ตัวนึงก็คือ Room มีหน้าที่ในการเก็บข้อมูลจาก repository แล้วนำมาแสดงผลต่อไปในแอพของเรา ซึ่งเขาบอกไว้ว่าควรใช้แทน SQLite อะเนอะ

งั้นเราค่อยๆเรียนรู้ไปพร้อมๆกันเนอะ จากคนที่ลืม SQL เพราะไม่ได้ใช้มานานมากแล้วหล่ะ

สร้าง Entity

  • เพิ่มเติมจาก model class อาจจะใช้แยกหรือรวมกันก็ได้
  • ใส่ชื่อตาราง เช่น @Entity(tableName = "blog_content_table") อันนี้ชื่อตารางคือ blog_content_table นั่นเอง
  • เพิ่ม PrimaryKey อาจจะใช้จาก id หรือให้มันสร้างใหม่ให้ก็ได้ แบบนี้ @PrimaryKey(autoGenerate = true) val primaryKey: Int โดยค่า PrimaryKey นั้นจะต้องเป็น type int, Integer, long และ Long เท่านั้นนะ ไม่งั้น build failed เพราะมันผิดหลักการของ SQL อ่ะ

ถ้าอ่านจากเว็บนี้จะประมาณว่า rowId หรือ primary key ถูกเก็บเป็น 64-bit signed integer โดยเป็น key ที่มีค่า unique ของข้อมูลในแต่ละ row นั่นเอง

SQLite Primary Key: The Ultimate Guide To Primary Key
This tutorial shows you how to use SQLite PRIMARY KEY constraint to define the primary key for a table.
  • แต่ละ column ในตารางใส่ @ColumnInfo(name = "word") ถ้าต้องการให้ชื่อ column ในตาราง ต่างจากชื่อ variable ที่เราตั้งชื่อไว้ก็ได้เช่นกัน ซึ่งใน data model class ซึ่งจะใส่หรือไม่ใส่ก็ได้แหละ โดยตัว column จะอ้างอิงจากชื่อของ attribute ข้างในนั่นเอง
  • พวกที่เป็น arrayList, list ต่างๆ ให้ใส่ @Embedded ไว้ด้านหน้า เพื่อให้มัน map กันได้
Android room persistent library - TypeConverter error of error: Cannot figure out how to save field to database”
Im not able to create a typeConverter in room due to an error. I seem to be following everything per the docs. I would like to convert a list to a json string. lets take a look at my entity: @
  • ก่อนหน้านี้ใช้ MutableList แล้วพังเรื่อง POJO แบบงงๆ
  • เราได้รับข้อมูลจากหลังบ้านเป็นก้อนที่เป็น list เนอะ ซึ่งจะถูกเขียนในแต่ละ row ของ table

ดังนั้น การ modified model class เพื่อรับกับการใช้ Room นั้น หน้าตาจะเป็นจะอี้~

สร้าง DAO

  • DAO (data access object) ความหมายมันก็คือการเข้าถึง object เราต้องระบุ SQL queries และ method เพื่อนำไปใช้งานต่อ โดย DAO ต้องเป็น interface หรือ abstract class เท่านั้น
  • ในที่นี้จะมีคำสั่งหลักๆ 3 อย่างคือ เรียกข้อมูลขึ้นมาอ่าน, ใส่ข้อมูลลงไป และลบข้อมูลทั้งหมด
  • common database operation ต่างๆที่ใช้ เช่น @Insert, @Delete, @Update แต่หลักๆจะใช้ @Query แล้วตามด้วย SQL command ด้านใน และเมื่อจะใส่ data เข้าไปใน database จะใช้ @Insert

สามารถอ่านเพิ่มเติมได้ที่นี่เลยจ้า

Accessing data using Room DAOs | Android Developers
Learn to modify database tables using data access objects (DAOs), a part of the Room Library

สร้าง database

  • ตัว Room จะเป็น database layer ที่อยู่ด้านบน SQLite database อีกทีจ้า
  • Room จะดูแลงานต่างๆ เหมือนกับที่เมื่อก่อนเราเขียน SQLiteOpenHelper เพื่อจัดการฐานข้อมูลภายในเครื่องนั่นแหละ
  • ก่อนอื่นเราสร้าง database ขึ้นมา 1 ตัว แบบนี้ โดย class นี้จะเป็น abstract class ที่ extend จาก RoomDatabase
  • ด้านบน class ใส่ annotate นามว่า @Database ตามมาด้วย entities คือ ก้อน Object ที่เราจะเก็บลง database แล้วก็เลข version อันนี้ก็ตรงไปตรงมาเนอะ แล้วก็ exportSchema ใส่เป็น false เมื่อไม่ต้องการเก็บ schema version history backups
  • ให้ database ของเราทำความรู้จักกับ DAO ที่เราสร้างไว้ โดยประกาศใน database สามารถมีได้หลาย DAO
abstract val blogContentDao : BlogContentDao
  • จากนั้นทำการสร้าง companion object ข้างในมีการประกาศตัวแปร INSTANCE โดย annotate ตัว @volatile
@Volatile
private var INSTANCE: BlogContentDatabase? = null
  • สร้าง function getBlogContentDatabase เพื่อสร้าง database ชุดนี้ขึ้นมา ถ้า database เป็น null โดยใช้ Room.databaseBuilder ใส่ applicationContext , database class และ ชื่อของ database
  • จากนั้นใส่ fallbackToDestructiveMigration() สำหรับ migration strategy คือการเอา row ทั้งหมดใน schema เดิม และ convert row เพื่อเข้า schema ใหม่ เมื่อ schema มีการเปลี่ยนแปลง ทำให้ data ของเรายังคงอยู่
  • และจบท้ายด้วย build() เป็นอันจบพิธีกรรม

สร้าง function ใน Repository เพื่อ insert ข้อมูลเข้า database

โครงสร้างของ Android Architecture Component แถวล่างๆจะมี Repository ใช่ม่ะ แล้วก็แตกไปสองทาง ก็คือ เรียก Network กับ Dao เนอะ

ตัว repository จะเป็นตัวจัดการเกี่ยวกับข้อมูลต่างๆ โดยจะเป็นคนตัดสินใจว่า จะ fetch data จาก Network ภายนอก หรือดึงจาก database ภายในมาใช้นั่นเอง

โดยเราจะทำการเรียกใช้ Dao ใน repository ดังนี้

  • เราจะเรียกใช้ Repository ผ่าน interface เพื่อทำการเรียกใช้และทำ Unit Test ได้ง่าย
  • เราเพิ่ม function ที่ชื่อว่า insertAllBlogToRoom เอาไว้ใช้ในการ insert data หลังจาก fetch data จาก network โดยจะทำการลบแล้วใส่ใหม่จ้า
  • และ getCachingBlogContent เอาไว้ get data ตอนที่ repository ตัดสินใจว่าจะดึง data ใน database ออกมานะ ก็คือตอน offline นั่นเอง

init Koin เพื่อเรียกใช้ database

แน่นอนว่าในที่นี้เราใช้ Koin ในการ inject ของต่างๆที่เป็น MVVM เนอะ ดังนั้นเราจึงต้อง init ตัว Room ที่ MainApplication เพิ่มด้วยนะ ดังนี้

โค้ดตรงนี้คือ example เท่านั้นเน้อ จริงๆเราจะ init กันมากกว่านี้เนอะ อันนี้คือยกมาเฉพาะส่วน database ให้ดูกัน

ผลคือเราไม่ต้องประกาศเรียกใช้ database ที่ทำไว้หล่ะ เนื่องจาก เราได้ทำการ inject ไปเรียบร้อยแล้ว เย่ะ

เรียกใช้ function ผ่าน ViewModel

เราจะทำการเรียกใช้ function insertAllBlogToRoom และ getCachingBlogContent ใน ViewModel โดยเรียกผ่าน Repository

ใน getBlogPost ทางเราจะแบ่งการทำงานเป็น 3 ส่วน คือ

  • ส่วนที่ fetch data จาก network : อันนี้เรียกปกติ เพิ่มเติมคือเราจะเอา data ที่ได้จาก api มาเก็บไว้ใน Room นั่นเอง
  • ส่วนที่ fetch data จาก database : แน่นอนว่าเรา fetch data จาก network แล้วเขาเคส failure เลยใช้ run มาต่อท้ายเพื่อทำ Job อื่นๆต่อ ก่อนที่จะเด้งไป exceptionHandler เนอะ โดยการที่เราโหลดข้อมูลที่เคยมีจาก Room มาใส่ LiveData เพื่อนำไปใช้งานต่อนั่นเอง แต่เราจะเก็บข้อมูลแค่หน้าแรกของทั้งหมดเท่านั้นเนอะ ในการเรียกใช้เราจะครอบ Coroutine ไปด้วยนะเออ เพื่อรอมัน query data และใช้ postValue แทน value ตามปกติ เพื่อให้มัน invoke ได้นั่นเอง
  • เมื่อไม่ได้อะไรเลย ก็จะเด้งไปที่ exceptionHandler โดยเราแสดงหน้าจอ failure ตามที่เราต้องการหล่ะ

ที่มาจ้า

MutableLiveData: Cannot invoke setValue on a background thread from Coroutine
I’m trying to trigger an update on LiveData from a coroutine: object AddressList: MutableLiveData<List<Address>>()fun getAddressesLiveData(): LiveData<List<Address>> {

เรียกใช้โดยการ observe ผ่าน LiveData จาก ViewModel ที่ View ที่เราต้องการ

อันนี้ก็ใช้งาน Observe ตามปกติเลยนะ

ก่อนหน้านี้อ่ะ failure case จะเป็นหน้าตานึง คราวนี้เราใส่ข้อมูลที่ได้จาก Room มาแทนที่ แบบนี้

ขอตัดตอนโค้ดมาเล่าเนื่องจากโค้ดจริงยาวมาก

  • ทำการเรียก blog content ทั้งหมด ตั้งแต่เริ่มสร้าง Activity
  • ทำการ Observe LiveData 2 ตัว คือ allBlogPost เป็นตัวที่ได้จาก API เนอะ จะเอาไปแสดง list ของบล็อกตามปกติ และ localBlogContentList เป็น caching data ที่เก็บไว้ใน Room เนอะ เราจะ Observe ต่อเมื่อเข้าเคส failure เท่านั้น โดยเรา Observe getBlogError มารอไว้ เพื่อไม่ให้ตอนที่ไม่ failure แสดงข้อมูลซํ้ากันเนอะ
  • ใน getBlogError เรามี condition ว่าถ้า tag เป็น null ก็คือเป็น default ที่เป็น All นี่แหละ ให้ดึงข้อมูลจาก Room มาแสดงได้เลย แต่ถ้าเป็น tag อื่นๆ ให้แสดงหน้าจอ try again ไปแล้วกันเนอะ

run app + ตรวจสอบข้อมูลที่ Database Inspector

เมื่อเราเสียบสาย บิ้วแอพเข้าไป ตู้มมมมมม เราจะเจอ icon ที่เป็นตารางหรือแว่นขยายแบบนี้ที่ไฟล์ DAO อ่ะเนอะ

ไอคอนตารางที่มีแว่นขยาย หน้าคำสั่ง SQL ต่างๆ

อ่ะกดเข้าไปหน่อย มันจะพาไปที่หน้า Database Inspector ถ้าเราเขียนโค้ดได้ถูกต้องเราจะได้เจอตาราง พร้อมกับข้อมูลในตาราง ดังนี้

ตารางหลักๆมี 2 ตัว คือ blog_content_table ที่ได้กล่าวไป เป็นการเก็บ content หน้าแรกของเรามาใน Room และ blog_tag_table อันนี้เป็นตัว tag ต่างๆในบล็อกของเรานั่นเอง

ปล. จริงๆค่าชื่อตารางเราใส่ในเป็นตัวแปรแหละ แต่เอาชื่อตรงๆมาแปะในบล็อกเพื่อให้ไม่งงเนอะ

มาดูในแอพกันเถอะ

https://www.youtube.com/watch?v=Etue7DxZt_g

พบว่าตัว tag นั้นได้หายไป ... แต่ถือว่าเราก็ caching data เพื่อนำไปแสดงตอน offline ได้แล้วแหละ ยังไงกดไปก็ไม่เจออะไรอยู่ดีแหละ

แต่ถึงอย่างนั้น เราก็ต้องเผื่อเคสที่มัน failure อยู่แล้วใช่ม้าาาาา

ส่วนเรื่อง Unit Testing จะพยายามมัดรวมไปอีกบล็อกนึงเนอะ

ปล. ถ้าเสียงตอบรับดีก็อาจจะมีตอนสองก็ได้มั้งนะ ดูก่อนๆ


แหล่งศึกษาและอ้างอิง มาจาก codelab อีกตามเคย

Android Kotlin Fundamentals: Create a Room Database
Learn how to use Room in your Android Kotlin apps. Room is a database library that's part of Android Jetpack. Room takes care of many of the chores of setting up and configuring a database, and makes it possible for your app to interact with the database using ordinary function calls.
Android Room with a View - Kotlin | Android Developers
In this codelab you build an Android app in Kotlin that uses Android Architecture Components (RoomDatabase, Entity, DAO, AndroidViewModel, LiveData) together with Kotlin coroutines. This sample app stores a list of words in a Room database and displays it in a RecyclerView. You will implement this a…
Android Kotlin Fundamentals: 6.2 Coroutines and Room
Learn how to use Kotlin coroutines in your Android app to move database operations away from the main thread.

อันนี้ไอเดียในการเกิดบล็อกนี้เนอะ

How to use room database as a cache
I am making an android app using MVVM architecture. I want to fetch data from an API and insert it into room database and then fetch it from the room in my app. I don’t know if it is a better way to
r/androiddev - Tips for implementing fully offline support
44 votes and 34 comments so far on Reddit

แต่ที่เราใช้นั้นค่อนข้างเกิน Codelab ไปไกลตรงใช้ Koin นี่แหละ


download แอพอ่านบล็อกใหม่ของเราได้ที่นี่

MikkiPastel - Apps on Google Play
First application from “MikkiPastel” on play store beta feature- read blog from https://www.mikkipastel.com by this application- read blog content by chrome custom tab- update or refresh new content by pull to refresh- share content to social network
https://play.google.com/store/apps/details?id=com.mikkipastel.blog

ติดตามข่าวสารและบทความใหม่ๆได้ที่

อย่าลืมกด like กด share บทความกันด้วยนะคะ :)

Posted by MikkiPastel on Sunday, 10 December 2017

และช่องทางใหม่ใน Twiter จ้า

Tags

Minseo Chayabanjonglerd

I am a full-time Android Developer and part-time contributor with developer community and web3 world, who believe people have hard skills and soft skills to up-skill to da moon.