Lớp học lập trình này thuộc khoá học Kiến thức cơ bản về Kotlin cho Android. Bạn sẽ nhận được nhiều giá trị nhất qua khoá học này nếu thực hiện các lớp học lập trình theo trình tự. Tất cả lớp học lập trình của khoá học đều được liệt kê trên trang đích của lớp học lập trình Kiến thức cơ bản về cách tạo ứng dụng Android bằng Kotlin.
Giới thiệu
Hầu hết các ứng dụng đều có dữ liệu cần lưu giữ, ngay cả sau khi người dùng đóng ứng dụng. Ví dụ: có thể ứng dụng đó lưu trữ danh sách phát, kho vật phẩm trong trò chơi, bản ghi chi phí và thu nhập, danh mục chòm sao hoặc dữ liệu về giấc ngủ theo thời gian. Thông thường, bạn sẽ sử dụng cơ sở dữ liệu để lưu trữ dữ liệu cố định.
Room
là một thư viện cơ sở dữ liệu thuộc Jetpack của Android. Room
đảm nhiệm nhiều công việc thiết lập và định cấu hình cơ sở dữ liệu, đồng thời giúp ứng dụng tương tác với cơ sở dữ liệu thông qua các lệnh gọi hàm thông thường. Về cơ bản, Room
là một lớp trừu tượng ở đầu cơ sở dữ liệu SQLite. thuật ngữ và cú pháp truy vấn của Room
đối với các truy vấn phức tạp hơn, hãy làm theo mô hình SQLite.
Hình ảnh dưới đây cho thấy cách cơ sở dữ liệu Room
phù hợp với kiến trúc tổng thể được đề xuất trong khoá học này.
Kiến thức bạn cần có
Bạn cần thông thạo:
- Tạo giao diện người dùng (UI) cơ bản cho một ứng dụng Android
- Sử dụng các hoạt động, mảnh và thành phần hiển thị.
- Điều hướng giữa các mảnh và sử dụng Safe Args (một trình bổ trợ Gradle) để truyền dữ liệu giữa các mảnh.
- Mô hình hiển thị, nhà máy mô hình hiển thị,
LiveData
và các đối tượng theo dõi của nó. Các chủ đề về Thành phần cấu trúc này được đề cập trong một lớp học lập trình trước đó của khoá học này. - Có kiến thức cơ bản về cơ sở dữ liệu SQL và ngôn ngữ SQLite. Hãy xem SQLite Primer để biết thông tin tổng quan nhanh hoặc thông tin gợi nhớ.
Kiến thức bạn sẽ học được
- Cách tạo và tương tác với cơ sở dữ liệu
Room
để duy trì dữ liệu. - Cách tạo một lớp dữ liệu xác định một bảng trong cơ sở dữ liệu.
- Cách sử dụng đối tượng truy cập dữ liệu (DAO) để ánh xạ các hàm Kotlin đến truy vấn SQL.
- Cách kiểm thử xem cơ sở dữ liệu của bạn có hoạt động hay không.
Bạn sẽ thực hiện
- Tạo cơ sở dữ liệu
Room
có giao diện cho dữ liệu giấc ngủ hằng đêm. - Kiểm thử cơ sở dữ liệu bằng các kiểm thử được cung cấp.
Trong lớp học lập trình này, bạn sẽ tạo phần cơ sở dữ liệu của một ứng dụng theo dõi chất lượng giấc ngủ. Ứng dụng này sử dụng cơ sở dữ liệu để lưu trữ dữ liệu giấc ngủ theo thời gian.
Ứng dụng này có 2 màn hình, được biểu thị bằng các mảnh, như minh hoạ trong hình bên dưới.
Màn hình đầu tiên (xuất hiện ở bên trái) có các nút để bắt đầu và dừng theo dõi. Màn hình này cho thấy tất cả dữ liệu về giấc ngủ của người dùng. Nút Xoá sẽ xoá vĩnh viễn tất cả dữ liệu mà ứng dụng đã thu thập cho người dùng.
Màn hình thứ hai (ở bên phải) là màn hình chọn mức đánh giá chất lượng giấc ngủ. Trong ứng dụng, điểm xếp hạng được biểu thị bằng số. Để phục vụ mục đích phát triển, ứng dụng này cho thấy cả biểu tượng khuôn mặt và giá trị tương đương bằng số của biểu tượng đó.
Luồng người dùng như sau:
- Người dùng mở ứng dụng và thấy màn hình theo dõi giấc ngủ.
- Người dùng nhấn vào nút Bắt đầu. Thao tác này sẽ ghi lại thời gian bắt đầu và hiển thị thời gian đó. Nút Start (Bắt đầu) bị vô hiệu hoá và nút Stop (Dừng) được bật.
- Người dùng nhấn vào nút Dừng. Thao tác này sẽ ghi lại thời gian kết thúc và mở màn hình chất lượng giấc ngủ.
- Người dùng chọn một biểu tượng chất lượng giấc ngủ. Màn hình đóng lại và màn hình theo dõi sẽ hiển thị thời gian kết thúc giấc ngủ và chất lượng giấc ngủ. Nút Stop (Dừng) bị tắt và nút Start (Bắt đầu) được bật. Ứng dụng đã sẵn sàng cho một đêm khác.
- Nút Xoá sẽ bật bất cứ khi nào có dữ liệu trong cơ sở dữ liệu. Khi người dùng nhấn vào nút Xoá, tất cả dữ liệu của họ sẽ bị xoá mà không có cách nào khôi phục được. Không có thông báo "Bạn có chắc chắn không?".
Ứng dụng này sử dụng một cấu trúc đơn giản, như minh hoạ dưới đây trong bối cảnh của cấu trúc đầy đủ. Ứng dụng chỉ sử dụng các thành phần sau:
- Bộ điều khiển giao diện người dùng
- Xem mô hình và
LiveData
- Cơ sở dữ liệu Room
Bước 1: Tải xuống và chạy ứng dụng khởi động
- Tải ứng dụng TrackMySleepQuality-Starter xuống từ GitHub.
- Tạo và chạy ứng dụng. Ứng dụng sẽ hiển thị giao diện người dùng cho mảnh
SleepTrackerFragment
nhưng không có dữ liệu. Các nút không phản hồi khi nhấn.
Bước 2: Kiểm tra ứng dụng khởi đầu
- Xem các tệp Gradle:
- Tệp Gradle của dự án
Trong tệpbuild.gradle
cấp dự án, hãy lưu ý đến các biến chỉ định phiên bản thư viện. Các phiên bản được dùng trong ứng dụng khởi đầu hoạt động tốt với nhau và hoạt động tốt với ứng dụng này. Khi bạn hoàn tất lớp học lập trình này, Android Studio có thể nhắc bạn cập nhật một số phiên bản. Bạn có thể chọn cập nhật hoặc giữ nguyên các phiên bản có trong ứng dụng. Nếu gặp phải lỗi biên dịch "lạ", hãy thử sử dụng tổ hợp các phiên bản thư viện mà ứng dụng giải pháp cuối cùng sử dụng. - Tệp Gradle của mô-đun. Lưu ý các phần phụ thuộc được cung cấp cho tất cả các thư viện Android Jetpack, bao gồm cả
Room
và các phần phụ thuộc cho coroutine.
- Hãy xem các gói và giao diện người dùng. Ứng dụng được cấu trúc theo chức năng. Gói này chứa các tệp phần giữ chỗ mà bạn sẽ thêm mã trong suốt loạt lớp học lập trình này.
- Gói
database
cho tất cả mã liên quan đến cơ sở dữ liệuRoom
. - Các gói
sleepquality
vàsleeptracker
chứa mảnh, mô hình xem và nhà máy mô hình xem cho từng màn hình.
- Hãy xem tệp
Util.kt
. Tệp này chứa các hàm giúp hiển thị dữ liệu về chất lượng giấc ngủ. Một số mã được đánh dấu là nhận xét vì mã đó tham chiếu đến một mô hình hiển thị mà bạn sẽ tạo sau. - Hãy xem thư mục androidTest (
SleepDatabaseTest.kt
). Bạn sẽ dùng kiểm thử này để xác minh rằng cơ sở dữ liệu hoạt động như dự kiến.
Trong Android, dữ liệu được biểu thị trong các lớp dữ liệu và dữ liệu được truy cập cũng như sửa đổi bằng cách sử dụng các lệnh gọi hàm. Tuy nhiên, trong lĩnh vực cơ sở dữ liệu, bạn cần có thực thể (entity) và truy vấn (query).
- Thực thể đại diện cho một đối tượng hoặc khái niệm cũng như các thuộc tính của đối tượng hoặc khái niệm đó để lưu trữ trong cơ sở dữ liệu. Một lớp thực thể xác định một bảng và mỗi phiên bản thể hiện của lớp đó đại diện cho một hàng trong bảng. Mỗi thuộc tính xác định một cột. Trong ứng dụng của bạn, thực thể này sẽ giữ thông tin về một đêm ngủ.
- Truy vấn là một yêu cầu về dữ liệu hoặc thông tin từ một bảng cơ sở dữ liệu hoặc tổ hợp các bảng, hoặc một yêu cầu thực hiện một hành động đối với dữ liệu. Các truy vấn phổ biến là để nhận, chèn và cập nhật các thực thể. Ví dụ: bạn có thể truy vấn tất cả các đêm ngủ được ghi lại, sắp xếp theo thời gian bắt đầu.
Room
sẽ giúp bạn thực hiện mọi thao tác khó khăn để chuyển từ các lớp dữ liệu Kotlin sang các thực thể có thể được lưu trữ trong các bảng SQLite, và từ các khai báo hàm sang các truy vấn SQL.
Bạn phải xác định từng thực thể dưới dạng một lớp dữ liệu được chú giải và các hoạt động tương tác dưới dạng một giao diện được chú giải, một đối tượng truy cập dữ liệu (DAO). Room
sử dụng các lớp được chú thích này để tạo bảng trong cơ sở dữ liệu và các truy vấn hoạt động trên cơ sở dữ liệu.
Bước 1: Tạo thực thể SleepNight
Trong nhiệm vụ này, bạn sẽ xác định một đêm ngủ dưới dạng lớp dữ liệu được chú giải.
Đối với một đêm ngủ, bạn cần ghi lại thời gian bắt đầu, thời gian kết thúc và mức đánh giá chất lượng.
Bạn cần có một mã nhận dạng riêng biệt cho mỗi đêm.
- Trong gói
database
, hãy tìm và mở tệpSleepNight.kt
. - Tạo lớp dữ liệu
SleepNight
có các tham số cho mã nhận dạng, thời gian bắt đầu (tính bằng mili giây), thời gian kết thúc (tính bằng mili giây) và điểm số chất lượng giấc ngủ dạng số.
- Bạn phải khởi chạy
sleepQuality
, vì vậy hãy đặt giá trị này thành-1
, cho biết rằng chưa có dữ liệu nào về chất lượng được thu thập. - Bạn cũng phải khởi tạo thời gian kết thúc. Đặt giá trị này thành thời gian bắt đầu để cho biết rằng chưa có thời gian kết thúc nào được ghi lại.
data class SleepNight(
var nightId: Long = 0L,
val startTimeMilli: Long = System.currentTimeMillis(),
var endTimeMilli: Long = startTimeMilli,
var sleepQuality: Int = -1
)
- Trước phần khai báo lớp, hãy chú giải lớp dữ liệu bằng
@Entity
. Đặt tên cho bảngdaily_sleep_quality_table
. Dù không bắt buộc nhưng bạn nên gán đối số chotableName
. Bạn có thể tra cứu các đối số khác trong tài liệu.
Nếu được nhắc, hãy nhậpEntity
và tất cả chú thích khác từ thư việnandroidx
.
@Entity(tableName = "daily_sleep_quality_table")
data class SleepNight(...)
- Để khai báo
nightId
làm khoá chính, hãy chú giải thuộc tínhnightId
bằng@PrimaryKey
. Hãy thiết lập tham sốautoGenerate
thànhtrue
đểRoom
tạo giá trị nhận dạng cho mỗi thực thể. Điều này đảm bảo rằng giá trị nhận dạng của mỗi đêm là giá trị duy nhất.
@PrimaryKey(autoGenerate = true)
var nightId: Long = 0L,...
- Chú giải các thuộc tính còn lại bằng
@ColumnInfo
. Tuỳ chỉnh tên thuộc tính bằng cách sử dụng các tham số như dưới đây.
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "daily_sleep_quality_table")
data class SleepNight(
@PrimaryKey(autoGenerate = true)
var nightId: Long = 0L,
@ColumnInfo(name = "start_time_milli")
val startTimeMilli: Long = System.currentTimeMillis(),
@ColumnInfo(name = "end_time_milli")
var endTimeMilli: Long = startTimeMilli,
@ColumnInfo(name = "quality_rating")
var sleepQuality: Int = -1
)
- Tạo và chạy mã để đảm bảo mã không có lỗi.
Trong nhiệm vụ này, bạn sẽ khai báo một đối tượng truy cập dữ liệu (DAO). Trên Android, DAO cung cấp các phương thức thuận tiện để chèn, xoá và cập nhật cơ sở dữ liệu.
Khi sử dụng cơ sở dữ liệu Room
, bạn sẽ truy vấn cơ sở dữ liệu bằng cách xác định và gọi các hàm Kotlin trong mã. Các hàm Kotlin này ánh xạ đến truy vấn SQL. Bạn xác định các mối liên kết đó trong một DAO bằng cách sử dụng chú giải và Room
sẽ tạo mã cần thiết.
Hãy coi DAO như việc xác định một giao diện tuỳ chỉnh để truy cập vào cơ sở dữ liệu của bạn.
Đối với các thao tác cơ sở dữ liệu phổ biến, thư viện Room
cung cấp các chú giải thuận tiện như @Insert
, @Delete
và @Update
. Chú giải @Query
dành cho các thao tác khác. Bạn có thể viết bất kỳ truy vấn nào được SQLite hỗ trợ.
Bên cạnh đó, khi bạn tạo truy vấn trong Android Studio, trình biên dịch sẽ kiểm tra các truy vấn SQL của bạn để tìm lỗi cú pháp.
Đối với cơ sở dữ liệu của trình theo dõi giấc ngủ, bạn cần có thể làm những việc sau:
- Chèn đêm lưu trú mới.
- Cập nhật một đêm hiện có để cập nhật thời gian kết thúc và mức đánh giá chất lượng.
- Tìm một đêm cụ thể dựa trên khoá của đêm đó.
- Tìm tất cả các đêm để bạn có thể hiển thị các đêm đó.
- Nhận thông tin về đêm gần đây nhất.
- Xoá tất cả các mục trong cơ sở dữ liệu.
Bước 1: Tạo DAO SleepDatabase
- Trong gói
database
, hãy mởSleepDatabaseDao.kt
. - Lưu ý rằng
interface
SleepDatabaseDao
được chú thích bằng@Dao
. Bạn cần chú thích tất cả DAO bằng từ khoá@Dao
.
@Dao
interface SleepDatabaseDao {}
- Bên trong phần nội dung của giao diện, hãy thêm chú giải
@Insert
. Bên dưới@Insert
, hãy thêm hàminsert()
, hàm này lấy một phiên bản của lớpEntity
SleepNight
làm đối số.
Vậy là xong.Room
sẽ tạo tất cả mã cần thiết để chènSleepNight
vào cơ sở dữ liệu. Khi bạn gọiinsert()
qua mã Kotlin,Room
sẽ thực thi một truy vấn SQL để chèn thực thể vào cơ sở dữ liệu. (Lưu ý: Bạn có thể gọi hàm này theo ý muốn.)
@Insert
fun insert(night: SleepNight)
- Thêm chú giải
@Update
có hàmupdate()
cho mộtSleepNight
. Thực thể được cập nhật là thực thể có cùng khoá với thực thể được truyền vào. Bạn có thể cập nhật một số hoặc tất cả thuộc tính khác của thực thể.
@Update
fun update(night: SleepNight)
Không có chú giải tiện lợi cho chức năng còn lại. Vì vậy, bạn phải sử dụng chú giải @Query
và cung cấp các truy vấn SQLite.
- Thêm chú giải
@Query
bằng hàmget()
. Hàm này nhận đối sốLong
key
và trả vềSleepNight
có thể rỗng. Bạn sẽ thấy lỗi về tham số còn thiếu.
@Query
fun get(key: Long): SleepNight?
- Truy vấn được cung cấp dưới dạng tham số chuỗi cho chú giải. Thêm một tham số vào
@Query
. Hãy tạo mộtString
là truy vấn SQLite.
- Chọn tất cả các cột qua
daily_sleep_quality_table
- Tìm
WHERE
cónightId
khớp với đối số :key
.
Hãy lưu ý đến:key
. Bạn sử dụng ký hiệu dấu hai chấm trong truy vấn để tham chiếu các đối số trong hàm.
("SELECT * from daily_sleep_quality_table WHERE nightId = :key")
- Thêm một
@Query
khác có hàmclear()
và truy vấn SQLite đểDELETE
mọi thứ từdaily_sleep_quality_table
. Truy vấn này không xoá chính bảng.
Chú giải@Delete
sẽ xoá một mục và bạn có thể sử dụng@Delete
rồi cung cấp danh sách các đêm cần xoá. Nhược điểm là bạn cần tìm nạp hoặc biết nội dung trong bảng. Chú giải@Delete
rất phù hợp để xoá các mục cụ thể, nhưng không hiệu quả khi xoá tất cả các mục khỏi một bảng.
@Query("DELETE FROM daily_sleep_quality_table")
fun clear()
- Thêm
@Query
có hàmgetTonight()
: Thiết lậpSleepNight
dogetTonight()
trả về là có thể có giá trị rỗng, để hàm có thể xử lý trường hợp bảng trống. (Bảng trống lúc ban đầu và sau khi dữ liệu bị xoá.)
Để lấy "tối nay" từ cơ sở dữ liệu, hãy viết một truy vấn SQLite trả về phần tử đầu tiên của danh sách kết quả được sắp xếp theonightId
theo thứ tự giảm dần. DùngLIMIT 1
để chỉ trả về một phần tử.
@Query("SELECT * FROM daily_sleep_quality_table ORDER BY nightId DESC LIMIT 1")
fun getTonight(): SleepNight?
- Thêm
@Query
có hàmgetAllNights()
:
- Yêu cầu truy vấn SQLite trả về tất cả cột trong
daily_sleep_quality_table
, theo thứ tự giảm dần. - Yêu cầu
getAllNights()
trả về danh sách các thực thểSleepNight
dưới dạngLiveData
.Room
giúpLiveData
này luôn được cập nhật, nghĩa là bạn chỉ cần xác định rõ ràng dữ liệu một lần. - Có thể bạn cần nhập
LiveData
từandroidx.lifecycle.LiveData
.
@Query("SELECT * FROM daily_sleep_quality_table ORDER BY nightId DESC")
fun getAllNights(): LiveData<List<SleepNight>>
- Dù bạn sẽ không nhận thấy thay đổi nào, nhưng hãy chạy ứng dụng của bạn để đảm bảo ứng dụng không có lỗi.
Trong nhiệm vụ này, bạn sẽ tạo một cơ sở dữ liệu Room
sử dụng Entity
và DAO mà bạn đã tạo trong nhiệm vụ trước đó.
Bạn cần tạo một lớp lưu giữ cơ sở dữ liệu trừu tượng, có chú giải bằng @Database
. Lớp này có một phương thức sẽ tạo một phiên bản của cơ sở dữ liệu nếu cơ sở dữ liệu không tồn tại hoặc trả về một giá trị tham chiếu đến cơ sở dữ liệu hiện có.
Việc lấy cơ sở dữ liệu Room
có chút phức tạp, vì vậy, sau đây là quy trình chung trước khi bạn bắt đầu với mã:
- Tạo một lớp
public abstract
extends RoomDatabase
. Lớp này đóng vai trò là một lớp lưu giữ cơ sở dữ liệu. Lớp này là lớp trừu tượng vìRoom
tạo mã triển khai cho bạn. - Chú thích lớp này bằng
@Database
. Trong các đối số, hãy khai báo các thực thể cho cơ sở dữ liệu và đặt số hiệu phiên bản. - Bên trong đối tượng
companion
, hãy xác định một phương thức hoặc thuộc tính trừu tượng sẽ trả vềSleepDatabaseDao
.Room
sẽ tạo nội dung cho bạn. - Bạn chỉ cần một phiên bản thể hiện của cơ sở dữ liệu
Room
cho toàn bộ ứng dụng, vì vậy hãy thiết lậpRoomDatabase
thành một singleton. - Chỉ sử dụng trình tạo cơ sở dữ liệu của
Room
để tạo cơ sở dữ liệu khi cơ sở dữ liệu này chưa tồn tại. Nếu không, hãy trả về cơ sở dữ liệu hiện có.
Bước 1: Tạo cơ sở dữ liệu
- Trong gói
database
, hãy mởSleepDatabase.kt
. - Trong tệp này, hãy tạo một lớp
abstract
có tên làSleepDatabase
, lớp này mở rộngRoomDatabase
.
Chú giải lớp bằng@Database
.
@Database()
abstract class SleepDatabase : RoomDatabase() {}
- Bạn sẽ thấy lỗi về việc thiếu các tham số thực thể và phiên bản. Chú giải
@Database
yêu cầu một số đối số đểRoom
có thể xây dựng cơ sở dữ liệu.
- Cung cấp
SleepNight
làm mục duy nhất có danh sáchentities
. - Đặt
version
thành1
. Mỗi lần thay đổi giản đồ, bạn sẽ phải tăng số hiệu phiên bản. - Thiết lập
exportSchema
thànhfalse
để không giữ lại các bản sao lưu nhật ký phiên bản giản đồ.
entities = [SleepNight::class], version = 1, exportSchema = false
- Cơ sở dữ liệu cần biết về DAO. Bên trong phần nội dung của lớp, hãy khai báo một giá trị trừu tượng trả về
SleepDatabaseDao
. Bạn có thể có nhiều DAO.
abstract val sleepDatabaseDao: SleepDatabaseDao
- Bên dưới đó, hãy xác định một đối tượng
companion
. Đối tượng đồng hành này cho phép các ứng dụng truy cập vào các phương thức tạo hoặc lấy cơ sở dữ liệu mà không cần khởi tạo lớp. Vì mục đích duy nhất của lớp này là cung cấp cơ sở dữ liệu, nên không có lý do gì để tạo thực thể cho lớp này.
companion object {}
- Bên trong đối tượng
companion
, hãy khai báoINSTANCE
(biến riêng tư có thể mang giá trị rỗng) cho cơ sở dữ liệu và khởi tạo biến đó đếnnull
. BiếnINSTANCE
sẽ giữ lại tham chiếu đến cơ sở dữ liệu này khi cơ sở dữ liệu được tạo. Điều này giúp bạn tránh việc mở kết nối đến cơ sở dữ liệu nhiều lần, điều này rất tốn kém.
Chú giải INSTANCE
bằng @Volatile
. Giá trị của biến volatile sẽ không bao giờ được lưu vào bộ nhớ đệm và tất cả lượt ghi và đọc sẽ xuất phát và trở về từ bộ nhớ chính. Điều này giúp đảm bảo giá trị của INSTANCE
luôn cập nhật và giống nhau cho tất cả các luồng thực thi. Tức là khi một luồng thực hiện thay đổi đối với INSTANCE
, tất cả luồng khác sẽ thấy thay đổi đó ngay lập tức, và bạn sẽ không gặp phải trường hợp chẳng hạn như 2 luồng cập nhật cùng một thực thể trong bộ nhớ đệm, điều này sẽ gây ra vấn đề.
@Volatile
private var INSTANCE: SleepDatabase? = null
- Bên dưới
INSTANCE
, vẫn bên trong đối tượngcompanion
, hãy khai báo một phương thứcgetInstance()
có tham sốContext
mà hàm tạo cơ sở dữ liệu sẽ cần đến. Trả về một loạiSleepDatabase
. Bạn sẽ thấy lỗi vìgetInstance()
chưa trả về giá trị nào.
fun getInstance(context: Context): SleepDatabase {}
- Bên trong
getInstance()
, hãy thêm một khốisynchronized{}
. Truyền vàothis
để bạn có thể truy cập vào ngữ cảnh.
Có thể nhiều luồng sẽ cùng lúc yêu cầu một phiên bản thể hiện của cơ sở dữ liệu, dẫn đến việc có hai cơ sở dữ liệu thay vì chỉ một. Vấn đề này khó xảy ra trong ứng dụng mẫu này, nhưng có thể xảy ra đối với một ứng dụng phức tạp hơn. Việc bao bọc mã để đưa cơ sở dữ liệu vàosynchronized
đồng nghĩa rằng tại mỗi thời điểm, chỉ có một luồng thực thi có thể nhập khối mã này, qua đó đảm bảo cơ sở dữ liệu chỉ được khởi tạo một lần.
synchronized(this) {}
- Bên trong khối được đồng bộ hoá, hãy sao chép giá trị hiện tại của
INSTANCE
vào một biến cục bộinstance
. Điều này là để tận dụng truyền thông minh, chỉ có sẵn cho các biến cục bộ.
var instance = INSTANCE
- Bên trong khối
synchronized
,return instance
ở cuối khốisynchronized
. Bỏ qua lỗi kiểu dữ liệu trả về không khớp; bạn sẽ không bao giờ trả về giá trị rỗng sau khi hoàn tất.
return instance
- Phía trên câu lệnh
return
, hãy thêm câu lệnhif
để kiểm tra xeminstance
có giá trị rỗng hay không, tức là chưa có cơ sở dữ liệu.
if (instance == null) {}
- Nếu
instance
lànull
, hãy sử dụng trình tạo cơ sở dữ liệu để tải cơ sở dữ liệu. Trong phần nội dung của câu lệnhif
, hãy gọiRoom.databaseBuilder
và cung cấp ngữ cảnh mà bạn đã truyền vào, lớp cơ sở dữ liệu và tên cho cơ sở dữ liệu,sleep_history_database
. Để xoá lỗi này, bạn sẽ phải thêm chiến lược di chuyển vàbuild()
trong các bước sau.
instance = Room.databaseBuilder(
context.applicationContext,
SleepDatabase::class.java,
"sleep_history_database")
- Thêm chiến lược di chuyển cần có vào hàm tạo. Sử dụng
.fallbackToDestructiveMigration()
.
Thông thường, bạn sẽ phải cung cấp một đối tượng di chuyển chứa chiến lược di chuyển khi giản đồ này thay đổi. Đối tượng di chuyển là một đối tượng sẽ xác định cách bạn lấy tất cả hàng trong giản đồ cũ và chuyển các hàng đó thành các hàng trong giản đồ mới để không bị mất dữ liệu. Quy trình di chuyển nằm ngoài phạm vi của lớp học lập trình này. Giải pháp đơn giản là huỷ bỏ và tạo lại cơ sở dữ liệu, tức là dữ liệu sẽ bị mất.
.fallbackToDestructiveMigration()
- Cuối cùng, hãy gọi
.build()
.
.build()
- Chỉ định
INSTANCE = instance
làm bước cuối cùng trong câu lệnhif
.
INSTANCE = instance
- Mã hoàn thiện của bạn sẽ có dạng như sau:
@Database(entities = [SleepNight::class], version = 1, exportSchema = false)
abstract class SleepDatabase : RoomDatabase() {
abstract val sleepDatabaseDao: SleepDatabaseDao
companion object {
@Volatile
private var INSTANCE: SleepDatabase? = null
fun getInstance(context: Context): SleepDatabase {
synchronized(this) {
var instance = INSTANCE
if (instance == null) {
instance = Room.databaseBuilder(
context.applicationContext,
SleepDatabase::class.java,
"sleep_history_database"
)
.fallbackToDestructiveMigration()
.build()
INSTANCE = instance
}
return instance
}
}
}
}
- Tạo và chạy mã của bạn.
Giờ đây, bạn đã có tất cả yếu tố nền móng để xử lý cơ sở dữ liệu Room
. Mã này sẽ biên dịch và chạy, nhưng bạn không có cách nào để biết mã có thực sự hoạt động hay không. Vì vậy, đây là thời điểm thích hợp để thêm một số kiểm thử cơ bản.
Bước 2: Kiểm thử SleepDatabase
Ở bước này, bạn chạy các kiểm thử được cung cấp để xác minh rằng cơ sở dữ liệu của bạn hoạt động. Điều này giúp đảm bảo cơ sở dữ liệu hoạt động trước khi bạn tạo cơ sở dữ liệu. Các bài kiểm thử được cung cấp là những bài kiểm thử cơ bản. Đối với một ứng dụng phát hành công khai, bạn sẽ thực hiện tất cả các hàm và truy vấn trong tất cả các DAO.
Ứng dụng khởi động có chứa một thư mục androidTest. Thư mục androidTest này chứa các kiểm thử đơn vị liên quan đến hoạt động đo lường Android. Đây là một cách diễn đạt hoa mỹ để nói rằng các kiểm thử cần có khung Android, vì vậy, bạn cần chạy các kiểm thử trên một thiết bị thực hoặc ảo. Dĩ nhiên, bạn cũng có thể tạo và chạy các kiểm thử đơn vị thuần tuý không liên quan đến khung ứng dụng Android.
- Trong Android Studio, trong thư mục androidTest, hãy mở tệp SleepDatabaseTest.
- Để bỏ chú thích mã, hãy chọn tất cả mã đã được chú thích rồi nhấn phím tắt
Cmd+/
hoặcControl+/
. - Hãy xem tệp này.
Sau đây là phần giải thích nhanh về mã kiểm thử, vì đây là một đoạn mã khác mà bạn có thể sử dụng lại:
SleepDabaseTest
là một lớp kiểm thử.- Chú thích
@RunWith
xác định trình chạy kiểm thử, là chương trình thiết lập và thực thi các kiểm thử. - Trong quá trình thiết lập, hàm được chú thích bằng
@Before
sẽ được thực thi và tạo mộtSleepDatabase
trong bộ nhớ bằngSleepDatabaseDao
. "Trong bộ nhớ" có nghĩa là cơ sở dữ liệu này không được lưu vào hệ thống tệp và sẽ bị xoá sau khi các kiểm thử chạy. - Ngoài ra, khi tạo cơ sở dữ liệu trong bộ nhớ, mã này sẽ gọi một phương thức khác dành riêng cho kiểm thử,
allowMainThreadQueries
. Theo mặc định, bạn sẽ gặp lỗi nếu cố gắng chạy các truy vấn trên luồng chính. Phương thức này cho phép bạn chạy các kiểm thử trên luồng chính. Bạn chỉ nên làm việc này trong quá trình kiểm thử. - Trong một phương thức kiểm thử được chú thích bằng
@Test
, bạn sẽ tạo, chèn và truy xuất mộtSleepNight
, đồng thời xác nhận rằng chúng giống nhau. Nếu có lỗi xảy ra, hãy gửi một ngoại lệ. Trong một kiểm thử thực tế, bạn sẽ có nhiều phương thức@Test
. - Khi quá trình kiểm thử hoàn tất, hàm được chú thích bằng
@After
sẽ thực thi để đóng cơ sở dữ liệu.
- Nhấp chuột phải vào tệp kiểm thử trong ngăn Project (Dự án) rồi chọn Run 'SleepDatabaseTest' (Chạy "SleepDatabaseTest").
- Sau khi các kiểm thử chạy, hãy xác minh trong ngăn SleepDatabaseTest rằng tất cả các kiểm thử đều đã đạt.
Vì tất cả các kiểm thử đều đạt, nên giờ đây bạn đã biết một số điều:
- Cơ sở dữ liệu được tạo đúng cách.
- Bạn có thể chèn một
SleepNight
vào cơ sở dữ liệu. - Bạn có thể lấy lại
SleepNight
. SleepNight
có giá trị chính xác cho chất lượng.
Dự án Android Studio: TrackMySleepQualityRoomAndTesting
Khi kiểm thử cơ sở dữ liệu, bạn cần thực hiện tất cả các phương thức được xác định trong DAO. Để hoàn tất quá trình kiểm thử, hãy thêm và thực thi các kiểm thử để thực hiện các phương thức DAO khác.
- Khai báo các bảng dưới dạng lớp dữ liệu được chú giải bằng
@Entity
. Khai báo các thuộc tính được chú giải bằng@ColumnInfo
dưới dạng cột trong bảng. - Khai báo một đối tượng truy cập dữ liệu (DAO) làm giao diện, kèm theo chú giải bằng
@Dao
. DAO ánh xạ các hàm Kotlin đến các truy vấn cơ sở dữ liệu. - Sử dụng chú giải để định nghĩa các hàm
@Insert
,@Delete
và@Update
. - Sử dụng chú giải
@Query
có chuỗi truy vấn SQLite làm tham số cho mọi truy vấn khác. - Tạo một lớp trừu tượng có hàm
getInstance()
trả về một cơ sở dữ liệu. - Sử dụng các kiểm thử đo lường để kiểm thử xem cơ sở dữ liệu và DAO của bạn có hoạt động như dự kiến hay không. Bạn có thể sử dụng các bài kiểm thử được cung cấp làm mẫu.
Khoá học của Udacity:
Tài liệu dành cho nhà phát triển Android:
RoomDatabase
Database
(chú thích)- Bạn có thể sử dụng truy vấn thô với
Room
Roomdatabase.Builder
- Kiểm thử quá trình huấn luyện
- Lớp
SQLiteDatabase
Dao
- Thư viện lưu trữ
Room
Các tài liệu và bài viết khác:
Phần này liệt kê các bài tập về nhà cho học viên của lớp học lập trình này trong phạm vi khoá học có người hướng dẫn. Người hướng dẫn phải thực hiện các việc sau đây:
- Giao bài tập về nhà nếu cần.
- Trao đổi với học viên về cách nộp bài tập về nhà.
- Chấm điểm bài tập về nhà.
Người hướng dẫn có thể sử dụng các đề xuất này ít hoặc nhiều tuỳ ý và nên giao cho học viên bất kỳ bài tập về nhà nào khác mà họ cảm thấy phù hợp.
Nếu bạn đang tự học các lớp học lập trình, hãy sử dụng những bài tập về nhà này để kiểm tra kiến thức của mình.
Trả lời các câu hỏi sau
Câu hỏi 1
Làm cách nào để bạn cho biết một lớp đại diện cho một thực thể sẽ lưu trữ trong cơ sở dữ liệu Room
?
- Mở rộng
DatabaseEntity
trong lớp. - Chú thích lớp này bằng
@Entity
. - Chú thích lớp này bằng
@Database
. - Mở rộng
RoomEntity
của lớp này rồi chú giải lớp này bằng@Room
.
Câu hỏi 2
DAO (đối tượng truy cập dữ liệu) là một giao diện mà Room
sử dụng để ánh xạ các hàm Kotlin với các truy vấn cơ sở dữ liệu.
Làm cách nào để bạn cho biết một giao diện đại diện cho DAO cho cơ sở dữ liệu Room
?
- Mở rộng
RoomDAO
trong giao diện. - Mở rộng
EntityDao
trong giao diện, sau đó triển khai phương thứcDaoConnection()
. - Chú thích giao diện này bằng
@Dao
. - Chú thích giao diện này bằng
@RoomConnection
.
Câu hỏi 3
Câu nào sau đây là đúng về cơ sở dữ liệu Room
? Hãy chọn tất cả phương án phù hợp.
- Bạn có thể xác định các bảng cho cơ sở dữ liệu
Room
dưới dạng các lớp dữ liệu được chú giải. - Nếu bạn trả về
LiveData
từ một truy vấn,Room
sẽ giữ choLiveData
được cập nhật cho bạn nếuLiveData
thay đổi. - Mỗi cơ sở dữ liệu
Room
phải có một và chỉ một DAO. - Để xác định một lớp là cơ sở dữ liệu
Room
, hãy tạo lớp đó thành một lớp con củaRoomDatabase
và chú thích lớp đó bằng@Database
.
Câu hỏi 4
Bạn có thể sử dụng chú giải nào sau đây trong giao diện @Dao
? Hãy chọn tất cả phương án phù hợp.
@Get
@Update
@Insert
@Query
Câu hỏi 5
Làm cách nào để xác minh rằng cơ sở dữ liệu của bạn đang hoạt động? Hãy chọn mọi câu trả lời phù hợp.
- Viết bài kiểm thử đo lường.
- Tiếp tục viết và chạy ứng dụng cho đến khi ứng dụng hiển thị dữ liệu.
- Thay thế các lệnh gọi đến các phương thức trong giao diện DAO bằng các lệnh gọi đến các phương thức tương đương trong lớp
Entity
. - Chạy hàm
verifyDatabase()
do thư việnRoom
cung cấp.
Chuyển sang bài học tiếp theo:
Để biết đường liên kết đến các lớp học lập trình khác trong khoá học này, hãy xem trang đích của lớp học lập trình Kiến thức cơ bản về cách tạo ứng dụng Android bằng Kotlin.