ทำ caching data ในแอพของเราผ่าน Room กันเถอะ
เธอเคยเห็น 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 นั่นเอง
- แต่ละ column ในตารางใส่
@ColumnInfo(name = "word")
ถ้าต้องการให้ชื่อ column ในตาราง ต่างจากชื่อ variable ที่เราตั้งชื่อไว้ก็ได้เช่นกัน ซึ่งใน data model class ซึ่งจะใส่หรือไม่ใส่ก็ได้แหละ โดยตัว column จะอ้างอิงจากชื่อของ attribute ข้างในนั่นเอง - พวกที่เป็น arrayList, list ต่างๆ ให้ใส่
@Embedded
ไว้ด้านหน้า เพื่อให้มัน map กันได้
- ก่อนหน้านี้ใช้ 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
สามารถอ่านเพิ่มเติมได้ที่นี่เลยจ้า
สร้าง 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 ตามที่เราต้องการหล่ะ
ที่มาจ้า
เรียกใช้โดยการ observe ผ่าน LiveData จาก ViewModel ที่ View ที่เราต้องการ
อันนี้ก็ใช้งาน Observe ตามปกติเลยนะ
ก่อนหน้านี้อ่ะ failure case จะเป็นหน้าตานึง คราวนี้เราใส่ข้อมูลที่ได้จาก Room มาแทนที่ แบบนี้
ขอตัดตอนโค้ดมาเล่าเนื่องจากโค้ดจริงยาวมาก
- ทำการเรียก blog content ทั้งหมด ตั้งแต่เริ่มสร้าง Activity
- ทำการ Observe LiveData 2 ตัว คือ
allBlogPost
เป็นตัวที่ได้จาก API เนอะ จะเอาไปแสดง list ของบล็อกตามปกติ และlocalBlogContentList
เป็น caching data ที่เก็บไว้ใน Room เนอะ เราจะ Observe ต่อเมื่อเข้าเคส failure เท่านั้น โดยเรา ObservegetBlogError
มารอไว้ เพื่อไม่ให้ตอนที่ไม่ failure แสดงข้อมูลซํ้ากันเนอะ - ใน
getBlogError
เรามี condition ว่าถ้า tag เป็น null ก็คือเป็น default ที่เป็น All นี่แหละ ให้ดึงข้อมูลจาก Room มาแสดงได้เลย แต่ถ้าเป็น tag อื่นๆ ให้แสดงหน้าจอ try again ไปแล้วกันเนอะ
run app + ตรวจสอบข้อมูลที่ Database Inspector
เมื่อเราเสียบสาย บิ้วแอพเข้าไป ตู้มมมมมม เราจะเจอ icon ที่เป็นตารางหรือแว่นขยายแบบนี้ที่ไฟล์ DAO อ่ะเนอะ
อ่ะกดเข้าไปหน่อย มันจะพาไปที่หน้า Database Inspector ถ้าเราเขียนโค้ดได้ถูกต้องเราจะได้เจอตาราง พร้อมกับข้อมูลในตาราง ดังนี้
ตารางหลักๆมี 2 ตัว คือ blog_content_table
ที่ได้กล่าวไป เป็นการเก็บ content หน้าแรกของเรามาใน Room และ blog_tag_table
อันนี้เป็นตัว tag ต่างๆในบล็อกของเรานั่นเอง
ปล. จริงๆค่าชื่อตารางเราใส่ในเป็นตัวแปรแหละ แต่เอาชื่อตรงๆมาแปะในบล็อกเพื่อให้ไม่งงเนอะ
มาดูในแอพกันเถอะ
พบว่าตัว tag นั้นได้หายไป ... แต่ถือว่าเราก็ caching data เพื่อนำไปแสดงตอน offline ได้แล้วแหละ ยังไงกดไปก็ไม่เจออะไรอยู่ดีแหละ
แต่ถึงอย่างนั้น เราก็ต้องเผื่อเคสที่มัน failure อยู่แล้วใช่ม้าาาาา
ส่วนเรื่อง Unit Testing จะพยายามมัดรวมไปอีกบล็อกนึงเนอะ
ปล. ถ้าเสียงตอบรับดีก็อาจจะมีตอนสองก็ได้มั้งนะ ดูก่อนๆ
แหล่งศึกษาและอ้างอิง มาจาก codelab อีกตามเคย
อันนี้ไอเดียในการเกิดบล็อกนี้เนอะ
แต่ที่เราใช้นั้นค่อนข้างเกิน Codelab ไปไกลตรงใช้ Koin นี่แหละ
download แอพอ่านบล็อกใหม่ของเราได้ที่นี่
ติดตามข่าวสารและบทความใหม่ๆได้ที่
และช่องทางใหม่ใน Twiter จ้า