Room database là gì

Bài viết gốc : //developer.android.com/training/data-storage/room

Lời mở đầu : Hi các em trong bài viết này thầy giới thiệu tới các em 1 cách thức mới để lưu trữ dữ liệu offline của lập trình Android với khái niệm Room

Room là gì ?

Chúng ta có thể hiểu Room là 1 bộ thư viện giúp lập trình viên tương tác dễ dàng hơn với SQLite bằng cách tái hiện lại cơ sở dữ liệu thông qua Entity và lớp DAO, khởi tạo cơ sở dữ liệu bằng RoomDatabase

Chúng ta cùng tìm hiểu cách sử dụng của Room nhé

Các em thêm dòng thư viện vào file build.gradle trước nhé :
[Phiên bản mới nhất khi thầy viết bài này là 2.1.0]

implementation "androidx.room:room-runtime:2.1.0" annotationProcessor "android.arch.persistence.room:compiler:2.1.0"

Để dễ hiểu, thầy sẽ tạo 1 bảng NguoiDung với các thuộc tính bao gồm id, ten, sdt là các cột như sau :

Bước 1 : tạo 1 Entity là class NguoiDung như sau :

Khai báo biến id là khóa chính với cú pháp @PrimaryKey phía trên biến
cột với cú pháp @ColumnInfo

Bước 2 : Khởi tạo các câu lệnh truy vấn

Bước 3 : Khởi tạo cơ sở dữ liệu

Cuối cùng chúng ta khởi tạo csdl trên MainActivity như sau :

Lưu ý : khi sử dụng các câu lênh truy vấn với Room thì chúng ta cần tạo AsyncTask để mở luồng riêng tránh gây lag cho thiết bị còn nếu chúng ta sử dụng luôn trên luồng chính thì thêm câu lệnh allowMainThreadQueries[] như hình trên

Test thử các câu lệnh lấy danh sách hoặc thêm, xóa User

Code mẫu : //github.com/huuhuybn/AndroidRoom

Các câu lệnh truy vấn tiêu biểu khác :

Truy vấn với câu điều kiện Where, tham số id truyền dưới cú pháp “: + tên biến”

Chúc các em code vui vẻ, mọi câu hỏi các em tạo bài trên nhóm Học lập trình Android – Huy Nguyễn nhé. 🙂

Trong bài viết cuối này của loạt bài Các Component Kiến Trúc của Android, chúng tôi sẽ khám phá thư viện Room persistence, một tài nguyên mới tuyệt vời giúp làm việc với cơ sở dữ liệu trên Android dễ dàng hơn nhiều. Nó cung cấp một abstract layer qua SQLite, các truy vấn SQL đã qua kiểm tra biên dịch và các truy vấn không đồng bộ và có thể quan sát được. Room đưa các hoạt động của cơ sở dữ liệu trên Android tới một đẳng cấp khác.

Vì đây là phần thứ tư của loạt bài viết, tôi sẽ giả định bạn đã quen với các khái niệm và các component của gói Kiến trúc, chẳng hạn như LiveData và LiveModel. Tuy nhiên, nếu bạn không đọc bất kỳ bài nào trog loạt 3 bài gần đây nhất, bạn vẫn có thể theo dõi. Nếu bạn không biết nhiều về các component này, hãy dành thời gian để đọc hết loạt bài - bạn có thể thích nó.

1. Room Component

Như đã đề cập, Room không phải là một hệ thống cơ sở dữ liệu mới. Đây là abstract layer gồm cơ sở dữ liệu chuẩn SQLite được Android thông qua. Tuy nhiên, Room bổ sung rất nhiều tính năng cho SQLite mà hầu như không thể nhận ra được. Room đơn giản hóa tất cả các hoạt động liên quan đến cơ sở dữ liệu và cũng làm cho chúng mạnh mẽ hơn nhiều vì nó cho phép khả năng trả về các sự kiện quan sát được và các truy vấn SQL đã được biên dịch.

Room bao gồm ba thành phần chính: Database, DAO [Data Access Object], và Entity. Mỗi bộ phận đều có nhiệm vụ riêng, và tất cả chúng cần được triển khai để hệ thống hoạt động. May mắn thay, việc triển khai chúng là khá đơn giản. Nhờ các chú thích đã được cung cấp và các abstract classes, boilerplate để triển khai Room được giữ ở mức tối thiểu.

  • Entity là class đang được lưu trong Database. Một bảng cơ sở dữ liệu duy nhất được tạo cho mỗi class được chú thích với @Entity.
  • DAO là interface được chú thích với @Dao, nó đóng vai trò trung gian truy cập vào các đối tượng trong cơ sở dữ liệu và các bảng của nó. Có bốn chú thích cụ thể cho các hoạt động cơ bản của DAO: @Insert, @Update, @Delete, and @Query.
  • Thành phần Database là một abstract class đã được chú giải bằng @Database, nó extends RoomDatabase. Class này định nghĩa một danh sách các Entities và các DAO của nó.

2- Thiết lập môi trường

Để dùng Room, bổ sung phần dependencies sau đây vào module ứng dụng trong Gradle:

compile "android.arch.persistence.room:runtime:1.0.0" annotationProcessor "android.arch.persistence.room:compiler:1.0.0"

Nếu bạn đang dùng Kotlin, bạn cần áp dụng kapt plugin và bổ sung một dependency khác.

apply plugin: 'kotlin-kapt' // … dependencies { // … kapt "android.arch.persistence.room:compiler:1.0.0" }

3. Entity, các bảng của Database

Một Entity đại diện cho object đang được lưu trong cơ sở dữ liệu. Mỗi class của Entity tạo ra một bảng cơ sở dữ liệu mới, với mỗi field đại diện cho một column. Chú thích được sử dụng để cấu hình các entities, và quá trình tạo ra chúng thực sự đơn giản. Lưu ý rất đơn giản để thiết lập một Entity bằng việc sử dụng các data classes của Kotlin.

@Entity data class Note[ @PrimaryKey[ autoGenerate = true ] var id: Long?, var text: String?, var date: Long? ]

Khi một class được chú thích với @Entity, thư viện Room sẽ tự động tạo một table sử dụng các fields của class như các column. Nếu bạn cần bỏ qua một field, chỉ cần chú thích nó với @Ignore. Mỗi entity cũng phải định nghĩa một @PrimaryKey.

Table và Columns [Bảng và các cột]

Room sẽ sử dụng class và các tên các field của nó để tự động tạo một table; tuy nhiên, bạn có thể cá nhân hoá table được tạo ra. Để xác định tên cho table, hãy sử dụng tùy chọn tableName trên chú thích @Entity và để chỉnh sửa tên column, thêm chú thích @ColumnInfo với tùy chọn tên của field. Điều quan trọng cần nhớ là tên của table và column phân biệt chữ hoa chữ thường.

@Entity[ tableName = “tb_notes” ] data class Note[ @PrimaryKey[ autoGenerate = true ] @ColumnInfo[ name = “_id” ] var id: Long?, //... ]

Những chỉ mục [indices] và những nguyên tắc duy nhất

Có một số nguyên tắc SQLite hữu ích mà Room cho phép chúng tôi dễ dàng thao tác trên các entities của chúng tôi. Để tăng tốc các truy vấn tìm kiếm, bạn có thể tạo các indices [chỉ mục] SQLite tại các fields có liên quan đến các truy vấn kiểu như vậy. Các indices sẽ làm cho các truy vấn tìm kiếm nhanh hơn; tuy nhiên, họ cũng sẽ insert, delete và update các truy vấn chậm hơn, vì vậy bạn phải sử dụng chúng cẩn thận. Hãy xem tài liệu SQLite để hiểu chúng tốt hơn.

Có hai cách khác nhau để tạo indices trong Room. Bạn chỉ cần đặt thuộc tính ColumnInfo, index, thành true, cho phép Room thiết lập các indices cho bạn.

@ColumnInfo[name = "date", index = true] var date: Long

Hoặc, nếu bạn cần kiểm soát tốt hơn, hãy sử dụng thuộc tính indices của chú giải @Entity, liệt kê tên của các fields phải biến thành index trong thuộc tính value. Lưu ý rằng thứ tự của các mục có giá trị rất quan trọng vì nó định nghĩa việc phân loại của bảng index.

@Entity[ tableName = "tb_notes", indices = arrayOf[ Index[ value = *arrayOf["date","title"], name = "idx_date_title" ] ] ]

Một nguyên tắc hữu ích khác của SQLite là duy nhất, ngăn không cho field đã đánh dấu có giá trị trùng lặp. Thật không may, trong phiên bản 1.0.0, Room không cung cấp thuộc tính này theo cách nó cần có, trực tiếp trên field của entity. Nhưng bạn có thể tạo một index và làm cho nó trở thành duy nhất, đạt được một kết quả tương tự.

@Entity[ tableName = "tb_users", indices = arrayOf[ Index[ value = “username”, name = "idx_username", unique = true ] ] ]

Các nguyên tắc ràng buộc khác như NOT NULL, DEFAULT và CHECK không có trong Room [cho đến bây giờ, ở phiên bản 1.0.0], nhưng bạn có thể tự tạo logic riêng cho Entity để có được kết quả tương tự Để tránh các giá trị null trên các entities của Kotlin, chỉ cần loại bỏ ? ở cuối của kiểu biến số hoặc, trong Java, bổ sung chú thích @NonNull.

Mối quan hệ giữa những Object

Không giống đa số các thư viện object-relational mapping, Room không cho phép một entity trực tiếp tham chiếu đến một entity khác. Điều này có nghĩa là nếu bạn có một entity được gọi là NotePad và một được gọi là Note, bạn không thể tạo một Collection of Notes bên trong NotePad như bạn sẽ làm với nhiều thư viện tương tự. Ban đầu, giới hạn này có vẻ phiền phức, nhưng đó là một quyết định thiết kế để điều chỉnh thư viện Room hợp với những hạn chế về kiến ​​trúc của Android. Để hiểu rõ hơn về quyết định này, hãy xem qua giải thích của Android về cách tiếp cận của họ.

Mặc dù mối quan hệ object của Room bị hạn chế, những nó vẫn tồn tại. Sử dụng các foreign keys, có thể tham chiếu các object cha và những objects con và kết nối điều chỉnh của chúng. Lưu ý rằng bạn cũng nên tạo ra một index trên object con để tránh việc quét toàn bộ table khi các object cha được bổ sung thay đổi.

@Entity[ tableName = "tb_notes", indices = arrayOf[ Index[ value = *arrayOf["note_date","note_title"], name = "idx_date_title" ], Index[ value = *arrayOf["note_pad_id"], name = "idx_pad_note" ] ], foreignKeys = arrayOf[ ForeignKey[ entity = NotePad::class, parentColumns = arrayOf["pad_id"], childColumns = arrayOf["note_pad_id"], onDelete = ForeignKey.CASCADE, onUpdate = ForeignKey.CASCADE ] ] ] data class Note[ @PrimaryKey[ autoGenerate = true ] @ColumnInfo[ name = "note_id" ] var id: Long, @ColumnInfo[ name = "note_title" ] var title: String?, @ColumnInfo[ name = "note_text" ] var text: String, @ColumnInfo[ name = "note_date" ] var date: Long, @ColumnInfo[ name = "note_pad_id"] var padId: Long ]

Các Objects nhúng vào

Có thể nhúng các objects vào bên trong các entities bằng cách sử dụng chú thích @Embedded. Khi một object được nhúng, tất cả các field của nó sẽ được thêm vào như các column trong table của entity, sử dụng các tên field của object được nhúng như các tên columns. Hãy xem xét đoạn code sau đây.

data class Location[ var lat: Float, var lon: Float ] @Entity[tableName = "tb_notes"] data class Note[ @PrimaryKey[ autoGenerate = true ] @ColumnInfo[ name = "note_id" ] var id: Long, @Embedded[ prefix = "note_location_" ] var location: Location? ]

Trong đoạn mã ở trên, class Location được nhúng trong entity tên gọi Note. Table của entity này sẽ có hai columns bổ sung, tương ứng với các fields của object được nhúng. Vì chúng ta đang sử dụng thuộc tính prefix trên chú thích @Embedded, tên của các columns sẽ là 'note_location_lat' và 'note_location_lon', và có thể tham chiếu những cột đó trong những câu truy vấn.

4. Data Access Object [Đối tượng truy cập dữ liệu]

Để truy cập Database của Room, cần thiết có một object DAO. DAO có thể được định nghĩa như là một interface hoặc một abstract class. Để triển khai nó, chú thích cho class hoặc interface với @Dao và thế là đủ để truy cập dữ liệu. Mặc dù có thể truy cập nhiều tables từ một DAO, nhưng chúng ta được khuyến nghị duy trì sử tách biệt của những sự việc - Seperation of Concerns, đây là tên của một kiến trúc tốt, và tạo ra một DAO chịu trách nhiệm cho việc truy cập vào từng entity.

@Dao interface NoteDAO{}

Insert, Update, and Delete [Thêm, Cập Nhật và Xoá]

Room cung cấp một loạt chú thích thuận tiện cho các hoạt động CRUD trong DAO: @Insert, @Update, @Delete, và @Query. Xử lý của @Insert nhận một entity duy nhất, một mảng [array], hoặc một List các entities làm tham số. Đối với các entity đơn lẻ, nó có trả về dạng long, đại biểu cho row được chèn vào với Insert. Đối với nhiều entity làm tham số, nó có thể trả về một mảng[array] long[] hoặc một danh sách [List] .

@Insert[ onConflict = OnConflictStrategy.REPLACE ] fun insertNote[note: Note]: Long @Insert[ onConflict = OnConflictStrategy.ABORT ] fun insertNotes[notes: List]: List

Như bạn thấy, có một property khác để bàn ở đây: onConflict. Nó định nghĩa chiến lược để làm theo trong trường hợp có xung đột bằng cách sử dụng hằng số OnConflictStrategy. Các tùy chọn đã tự giải thích khá rõ, với ABORT, FAIL, và REPLACE là những khả năng xử lý quan trọng.

Để cập nhật các entities, chúng ta sử dụng chú thích @Update. Nó tuân theo nguyên tắc giống với @Insert, nhận một hoặc nhiều entities làm tham số. Room sẽ sử dụng những entity được nhận để cập nhật các giá trị của nó, sử dụng PrimaryKey của entity để làm tham chiếu. Tuy nhiên, @Update chỉ có thể trả về một kiểu giá trị int để hiển thị cho tổng số các rows đã được cập nhật.

@Update[] fun updateNote[note: Note]: Int

Một lần nữa, theo nguyên tắc tương tự, chú thích @Delete có thể tiếp nhận một hoặc nhiều entity và trả về một int với tổng số các rows của table đã được cập nhật. Nó cũng sử dụng PrimaryKey của entity để tìm và gỡ bỏ đăng ký trong table của cơ sở dữ liệu.

@Delete fun deleteNote[note: Note]: Int

Tạo những câu truy vấn

Cuối cùng, chú thích @Query thực hiện tham vấn trong cơ sở dữ liệu. Các truy vấn được xây dựng theo cách tương tự với các truy vấn SQLite, với sự khác biệt lớn nhất là khả năng nhận các tham số trực tiếp từ các phương thức. Nhưng đặc điểm quan trọng nhất là các truy vấn được xác minh tại thời điểm biên dịch, có nghĩa là trình biên dịch sẽ tìm ra lỗi ngay khi bạn build dự án.

Để tạo câu truy vấn, hãy chú thích một phương thức với @Query và viết một truy vấn SQLite. Chúng tôi sẽ không bận tâm quá nhiều đến cách viết truy vấn vì họ sử dụng SQLite chuẩn. Nhưng nói chung, bạn sẽ sử dụng truy vấn để lấy dữ liệu từ cơ sở dữ liệu bằng cách sử dụng lệnh SELECT. Những giá trị thu về có thể là một hoặc nhiều giá trị.

@Query["SELECT * FROM tb_notes"] fun findAllNotes[]: List

Nó thực sự đơn giản để truyền các tham số cho các truy vấn. Phòng sẽ suy ra tên của tham số, sử dụng tên đối số phương pháp. Để truy cập nó, sử dụng :, theo sau là tên.

@Query["SELECT * FROM tb_notes WHERE note_id = :id"] fun findNoteById[id: Long]: Note @Query[“SELECT * FROM tb_noted WHERE note_date BETWEEN :early AND :late”] fun findNoteByDate[early: Date, late: Date]: List

Các truy vấn LiveData

Room được thiết kế để làm việc tốt với LiveData. Đối với một @Query để trả về một LiveData, chỉ cần tuân theo tiêu chuẩn của LiveData và thế là đủ tốt.

@Query["SELECT * FROM tb_notes WHERE note_id = :id"] fun findNoteById[id: Long]: LiveData

Sau đó, sẽ có thể quan sát kết quả truy vấn và nhận được kết quả không đồng bộ một cách dễ dàng. Nếu bạn không biết sức mạnh của LiveData, hãy dành thời gian để đọc hướng dẫn của chúng tôi về component này.

5. Xây dựng Cơ sở dữ liệu

Cơ sở dữ liệu được tạo ra bởi một abstract class, được chú thích với @Database và extend class RoomDatabase. Ngoài ra, các entities sẽ được quản lý bởi cơ sở dữ liệu phải được thông qua trong một mảng trong thuộc tính của entities trong chú thích @Database.

@Database[ entities = arrayOf[ NotePad::class, Note::class ] ] abstract class Database : RoomDatabase[] { abstract fun padDAO[]: PadDAO abstract fun noteDAO[]: NoteDAO }

Một khi class database được triển khai, đó là thời gian để xây dựng. Điều quan trọng cần nhấn mạnh là giá trị database nên được xây dựng chỉ một lần trong mỗi phiên làm việc, và cách tốt nhất để đạt được điều này là sử dụng một hệ thống jnection dependency, chẳng hạn như Dagger. Tuy nhiên, chúng ta sẽ không đào sâu vào DI bây giờ, vì nó nằm ngoài phạm vi của hướng dẫn này.

fun providesAppDatabase[] : Database { return Room.databaseBuilder[ context, Database::class.java, "database"] .build[] }

Thông thường, các thao tác trên cơ sở dữ liệu Room không thể được thực hiện từ UI Thread, vì chúng đang chặn lại và có thể tạo ra sự cố cho hệ thống. Tuy nhiên, nếu bạn muốn ép buộc xử lý trên UI Thread, bổ sung allowMainThreadQueries để xây dựng các tùy chọn. Trong thực tế, có nhiều lựa chọn thú vị cho cách xây dựng cơ sở dữ liệu, và tôi khuyên bạn nên đọc tài liệu RoomDatabase.Builder để hiểu rỏ các khả năng này.

6. Datatype và Data Conversion

Một Datatype của column được tự động định nghĩa với Room. Hệ thống sẽ suy ra từ kiểu của field, tương xứng với loại Datatype của SQLite. Hãy nhớ rằng đa số POJO của Java sẽ được chuyển đổi; tuy nhiên, cần tạo ra trình chuyển đổi dữ liệu để xử lý các đối tượng phức tạp hơn không được Room tự động nhận diện, chẳng hạn như Date và Enum.

Đối với Room, việc hiểu phần chuyển đổi dữ liệu, là cần thiết để cung cấp TypeConverters và đăng ký những bộ chuyển đổi trong Room. Có thể làm cho việc đăng ký này cần cân nhắc ở tình huống cụ thể - ví dụ: nếu bạn đăng ký TypeConverter trong Database, thì tất cả các entity của cơ sở dữ liệu sẽ sử dụng bộ chuyển đổi. Nếu bạn đăng ký trên một entity, chỉ có các thuộc tính của entity đó mới có thể sử dụng nó.

Để chuyển đổi một đối tượng Date trực tiếp thành loại Long trong suốt quá trình hoạt động lưu của Room và sau đó chuyển đổi Long thành loại Date khi tham chiếu cơ sở dữ liệu, trước tiên hãy khai báo một TypeConverter.

class DataConverters { @TypeConverter fun fromTimestamp[mills: Long?]: Date? { return if [mills == null] null else Date[mills] } @TypeConverter fun fromDate[date: Date?]: Long? = date?.time }

Sau đó, đăng ký TypeConverter trong Database, hoặc trong một tình huống cụ thể hơn nếu bạn muốn.

@Database[ entities = arrayOf[ NotePad::class, Note::class ], version = 1 ] @TypeConverters[DataConverters::class] abstract class Database : RoomDatabase[]

7. Sử dụng Room trong một ứng dụng

Ứng dụng chúng tôi đã phát triển trong loạt bài này đã sử dụng SharedPreferences để lưu trữ [dạng cache] dữ liệu thời tiết. Bây giờ chúng ta đã biết cách sử dụng Room, chúng ta sẽ sử dụng nó để tạo ra một bộ nhớ cache phức tạp hơn sẽ cho phép chúng ta lấy dữ liệu đã lưu dựa theo thành phố và cũng xem xét ngày thời tiết trong quá trình lấy dữ liệu.

Trước hết, chúng ta hãy tạo ra entity của chúng ta. Chúng tôi sẽ lưu tất cả dữ liệu của chúng tôi chỉ sử dụng class WeatherMain. Chúng ta chỉ cần thêm một số chú thích cho class, và chúng ta đã hoàn tất.

@Entity[ tableName = "weather" ] data class WeatherMain[ @ColumnInfo[ name = "date" ] var dt: Long?, @ColumnInfo[ name = "city" ] var name: String?, @ColumnInfo[name = "temp_min" ] var tempMin: Double?, @ColumnInfo[name = "temp_max" ] var tempMax: Double?, @ColumnInfo[ name = "main" ] var main: String?, @ColumnInfo[ name = "description" ] var description: String?, @ColumnInfo[ name = "icon" ] var icon: String? ] { @ColumnInfo[name = "id"] @PrimaryKey[autoGenerate = true] var id: Long = 0 // ...

Chúng ta cũng cần một DAQ. WeatherDAO sẽ quản lý các hoạt động CRUD trong entry của chúng ta. Chú ý rằng tất cả truy vấn đang trở về LiveData.

@Dao interface WeatherDAO { @Insert[ onConflict = OnConflictStrategy.REPLACE ] fun insert[ w: WeatherMain ] @Delete fun remove[ w: WeatherMain ] @Query[ "SELECT * FROM weather " + "ORDER BY id DESC LIMIT 1" ] fun findLast[]: LiveData @Query["SELECT * FROM weather " + "WHERE city LIKE :city " + "ORDER BY date DESC LIMIT 1"] fun findByCity[city: String ]: LiveData @Query["SELECT * FROM weather " + "WHERE date < :date " + "ORDER BY date ASC LIMIT 1" ] fun findByDate[ date: Long ]: List }

Cuối cùng, đây là thời diểm để tạo cơ sỡ dữ liệu.

@Database[ entities = arrayOf[WeatherMain::class], version = 2 ] abstract class Database : RoomDatabase[] { abstract fun weatherDAO[]: WeatherDAO }

Ok, bây giờ chúng ta đã cấu hình cơ sở dữ liệu Room của chúng ta. Tất cả những gì còn lại để làm là đưa nó lên với Dagger và bắt đầu sử dụng nó. Trong DataModule, hãy cung cấp Database và WeatherDAO.

@Module class DataModule[ val context: Context ] { // ... @Provides @Singleton fun providesAppDatabase[] : Database { return Room.databaseBuilder[ context, Database::class.java, "database"] .allowMainThreadQueries[] .fallbackToDestructiveMigration[] .build[] } @Provides @Singleton fun providesWeatherDAO[database: Database] : WeatherDAO { return database.weatherDAO[] } }

Như bạn nên nhớ, chúng tôi có một repo chịu trách nhiệm xử lý tất cả các hoạt động dữ liệu. Hãy tiếp tục sử dụng class này cho yêu cầu dữ liệu Room của ứng dụng Nhưng trước tiên, chúng ta cần chỉnh sửa phương thức provideMainRepository của DataModule, bao gồm WeatherDAO trong suốt quá trình xây dựng lớp.

@Module class DataModule[ val context: Context ] { //... @Provides @Singleton fun providesMainRepository[ openWeatherService: OpenWeatherService, prefsDAO: PrefsDAO, weatherDAO: WeatherDAO, locationLiveData: LocationLiveData ] : MainRepository { return MainRepository[ openWeatherService, prefsDAO, weatherDAO, locationLiveData ] } /… }

Hầu hết các phương thức mà chúng ta sẽ thêm vào MainRepository khá đơn giản. Tuy nhiên, nó đáng tìm kiếm kỹ hơn về clearOldData[]. Thao tác này sẽ xóa tất cả dữ liệu cũ hơn một ngày, chỉ duy trì dữ liệu thời tiết có liên quan được lưu trong cơ sở dữ liệu.

class MainRepository @Inject constructor[ private val openWeatherService: OpenWeatherService, private val prefsDAO: PrefsDAO, private val weatherDAO: WeatherDAO, private val location: LocationLiveData ] : AnkoLogger { fun getWeatherByCity[ city: String ] : LiveData { info["getWeatherByCity: $city"] return openWeatherService.getWeatherByCity[city] } fun saveOnDb[ weatherMain: WeatherMain ] { info["saveOnDb:\n$weatherMain"] weatherDAO.insert[ weatherMain ] } fun getRecentWeather[]: LiveData { info["getRecentWeather"] return weatherDAO.findLast[] } fun getRecentWeatherForLocation[location: String]: LiveData { info["getWeatherByDateAndLocation"] return weatherDAO.findByCity[location] } fun clearOldData[]{ info["clearOldData"] val c = Calendar.getInstance[] c.add[Calendar.DATE, -1] // get weather data from 2 days ago val oldData = weatherDAO.findByDate[c.timeInMillis] oldData.forEach{ w -> info["Removing data for '${w.name}':${w.dt}"] weatherDAO.remove[w] } } // ... }

MainViewModel chịu trách nhiệm tham vấn tới repo của chúng tôi. Hãy bổ sung một số logic để giải quyết các hoạt động của chúng tôi tới cơ sở dữ liệu Room. Đầu tiên, chúng ta thêm một MutableLiveData, weatherDB chịu trách nhiệm tham vấn cho MainRepository. Sau đó, chúng tôi loại bỏ các tham chiếu đến SharedPreferences, làm cho bộ nhớ cache của chúng tôi chủ yếu dựa vào Room database.

class MainViewModel @Inject constructor[ private val repository: MainRepository ] : ViewModel[], AnkoLogger { // … // Weather saved on database private var weatherDB: LiveData = MutableLiveData[] // … // We remove the consultation to SharedPreferences // making the cache exclusive to Room private fun getWeatherCached[] { info["getWeatherCached"] weatherDB = repository.getRecentWeather[] weather.addSource[ weatherDB, { w -> info["weatherDB: DB: \n$w"] weather.postValue[ApiResponse[data = w]] weather.removeSource[weatherDBSaved] } ] }

Để làm cho bộ nhớ cache của chúng tôi phù hợp, chúng tôi sẽ xóa dữ liệu cũ mỗi khi có dự đoán mới về thời tiết.

private var weatherByLocationResponse: LiveData = Transformations.switchMap[ location, { l -> info["weatherByLocation: \nlocation: $l"] doAsync { repository.clearOldData[] } return@switchMap repository.getWeatherByLocation[l] } ] private var weatherByCityResponse: LiveData = Transformations.switchMap[ cityName, { city -> info["weatherByCityResponse: city: $city"] doAsync { repository.clearOldData[] } return@switchMap repository.getWeatherByCity[city] } ]

Cuối cùng, chúng ta sẽ lưu dữ liệu vào cơ sở dữ liệu Room mỗi khi thời tiết mới được nhận.

// Receives updated weather response, // send it to UI and also save it private fun updateWeather[w: WeatherResponse]{ info["updateWeather"] // getting weather from today val weatherMain = WeatherMain.factory[w] // save on shared preferences repository.saveWeatherMainOnPrefs[weatherMain] // save on db repository.saveOnDb[weatherMain] // update weather value weather.postValue[ApiResponse[data = weatherMain]] }

Bạn có thể xem code hoàn chỉnh trong repo GitHub của bài viết này.

Tổng kết

Cuối cùng, chúng ta đang ở phần tổng kết của loạt bài Các Components Kiến trúc Android. Những công cụ này sẽ là những người bạn đồng hành tuyệt vời trong cuộc hành trình phát triển Android của bạn. Tôi khuyên bạn nên tiếp tục khám phá các components. Hãy thử dành thời gian để đọc tài liệu.

Và kixem qua một số bài viết khác của chúng tôi về phát triển ứng dụng Android trên Envato Tuts+!

Video liên quan

Chủ Đề