1. Trước khi bắt đầu
Hầu hết ứng dụng đủ chất lượng để phát hành công khai đều có dữ liệu mà ứng dụng cần duy trì. Ví dụ: có thể ứng dụng đó lưu trữ danh sách phát bài hát, các mục trong danh sách việc cần làm, bản ghi chi phí và thu nhập, danh mục chòm sao hoặc bản ghi dữ liệu cá nhân. Trong những trường hợp sử dụng này, bạn sẽ sử dụng cơ sở dữ liệu để lưu trữ những dữ liệu cố định (persistent data).
Room là một thư viện dữ liệu cố định, thuộc Jetpack của Android. Room là một tầng trừu tượng ở đầu cơ sở dữ liệu SQLite. SQLite sử dụng một ngôn ngữ chuyên biệt (SQL) để thực hiện các thao tác liên quan đến cơ sở dữ liệu. Thay vì trực tiếp sử dụng SQLite, Room đơn giản hoá các công việc thiết lập cơ sở dữ liệu, định cấu hình và tương tác với ứng dụng. Room cũng cho phép kiểm tra thời gian biên dịch của các câu lệnh SQLite.
Tầng trừu tượng (abstraction layer) là một tập hợp gồm nhiều hàm giúp ẩn phương thức triển khai/độ phức tạp cơ bản. Tầng trừu tượng cung cấp giao diện cho một nhóm chức năng hiện có, chẳng hạn như SQLite trong trường hợp này.
Hình ảnh dưới đây cho thấy cách Room (trong vai trò nguồn dữ liệu) hoạt động với cấu trúc tổng thể được đề xuất trong khoá học này. Room là một Nguồn dữ liệu.
Điều kiện tiên quyết
- Có thể xây dựng giao diện người dùng (UI) cơ bản cho một ứng dụng Android bằng Jetpack Compose.
- Có thể sử dụng các thành phần kết hợp (composable) như
Text
,Icon
,IconButton
vàLazyColumn
. - Có thể sử dụng thành phần kết hợp
NavHost
để xác định các tuyến và màn hình trong ứng dụng. - Có thể điều hướng giữa các màn hình bằng
NavHostController
. - Quen thuộc với thành phần cấu trúc Android
ViewModel
. Có thể sử dụngViewModelProvider.Factory
để tạo ViewModel. - Quen thuộc với các nguyên tắc cơ bản về cơ chế xử lý đồng thời.
- Có thể sử dụng coroutine cho các tác vụ chạy trong thời gian dài.
- Có kiến thức cơ bản về cơ sở dữ liệu SQLite và ngôn ngữ SQL.
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 SQLite bằng thư viện Room.
- Cách tạo thực thể, đối tượng truy cập dữ liệu (DAO) và các lớp cơ sở dữ liệu.
- Cách sử dụng DAO để ánh xạ hàm Kotlin đến truy vấn SQL.
Sản phẩm bạn sẽ tạo ra
- Bạn sẽ tạo một ứng dụng Inventory (Kiểm kho) có chức năng lưu các mặt hàng tồn kho vào cơ sở dữ liệu SQLite.
Bạn cần có
- Đoạn mã khởi đầu cho ứng dụng Inventory
- Máy tính đã cài đặt Android Studio
- Thiết bị hoặc trình mô phỏng có API cấp 26 trở lên
2. Tổng quan về ứng dụng
Trong lớp học lập trình này, bạn sẽ xử lý một đoạn mã khởi đầu của ứng dụng Inventory và dùng thư viện Room để thêm tầng cơ sở dữ liệu vào ứng dụng này. Phiên bản hoàn thiện của ứng dụng cho thấy một danh sách các mặt hàng qua cơ sở dữ liệu kho hàng. Người dùng có thể chọn thêm mặt hàng mới, cập nhật mặt hàng hiện có và xoá mặt hàng trong cơ sở dữ liệu kho hàng. Đối với lớp học lập trình này, bạn sẽ lưu dữ liệu mặt hàng vào cơ sở dữ liệu Room. Bạn sẽ hoàn tất chức năng còn lại của ứng dụng trong lớp học lập trình tiếp theo.
3. Tổng quan về ứng dụng khởi đầu
Tải đoạn mã khởi đầu cho lớp học lập trình này
Để bắt đầu, hãy tải đoạn mã khởi đầu xuống:
Ngoài ra, bạn có thể sao chép kho lưu trữ GitHub cho mã:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-inventory-app.git $ cd basic-android-kotlin-compose-training-inventory-app $ git checkout starter
Bạn có thể duyệt xem đoạn mã này trong kho lưu trữ GitHub Inventory app
.
Tổng quan về đoạn mã khởi đầu
- Mở dự án có đoạn mã khởi đầu trong Android Studio.
- Chạy ứng dụng trên thiết bị Android hoặc trên trình mô phỏng. Đảm bảo trình mô phỏng hoặc thiết bị được kết nối chạy API cấp 26 trở lên. Trình kiểm tra cơ sở dữ liệu hoạt động trên trình mô phỏng/thiết bị chạy API cấp 26 trở lên.
- Lưu ý rằng ứng dụng không cho thấy dữ liệu kho hàng.
- Hãy nhấn vào nút hành động nổi (FAB). Nút này cho phép bạn thêm các mặt hàng mới vào cơ sở dữ liệu.
Ứng dụng sẽ chuyển đến màn hình mới để bạn có thể nhập thông tin chi tiết về mặt hàng mới.
Vấn đề trong đoạn mã khởi đầu
- Trên màn hình Add Item (Thêm mặt hàng), hãy nhập thông tin chi tiết của một mặt hàng như tên, giá và số lượng của mặt hàng đó.
- Nhấn vào Save (Lưu). Màn hình Add Item (Thêm mặt hàng) không đóng, nhưng bạn có thể quay lại bằng phím Quay lại. Chức năng lưu chưa được triển khai, vì vậy, thông tin chi tiết về mặt hàng không được lưu.
Hãy để ý rằng ứng dụng chưa hoàn thiện và chức năng của nút Save (Lưu) chưa được triển khai.
Trong lớp học lập trình này, bạn sẽ thêm đoạn mã sử dụng Room để lưu thông tin kho hàng trong cơ sở dữ liệu SQLite. Bạn sẽ dùng thư viện Room về dữ liệu cố định để tương tác với cơ sở dữ liệu SQLite.
Hướng dẫn về đoạn mã
Đoạn mã khởi đầu bạn tải xuống có bố cục màn hình được thiết kế sẵn cho bạn. Trong lộ trình này, bạn sẽ tập trung vào việc triển khai logic cơ sở dữ liệu. Phần sau đây là hướng dẫn ngắn gọn về một số tệp để bạn bắt đầu.
ui/home/HomeScreen.kt
Tệp này là màn hình chính hoặc màn hình đầu tiên của ứng dụng, chứa thành phần kết hợp để cho bạn thấy danh sách mặt hàng tồn kho. Tệp này có FAB để thêm mặt hàng mới vào danh sách. Theo lộ trình học tập thì bạn sẽ cho thấy các mặt hàng trong danh sách ở phần sau.
ui/item/ItemEntryScreen.kt
Màn hình này tương tự như ItemEditScreen.kt
. Cả hai đều có trường văn bản để cho thấy thông tin chi tiết về mặt hàng. Màn hình này xuất hiện khi người dùng nhấn vào FAB trên màn hình chính. ItemEntryViewModel.kt
là ViewModel
tương ứng cho màn hình này.
ui/navigation/InventoryNavGraph.kt
Tệp này là biểu đồ điều hướng cho toàn bộ ứng dụng.
4. Các thành phần chính của Room
Kotlin giúp bạn xử lý dữ liệu dễ dàng thông qua các lớp dữ liệu (data class). Tuy bạn có thể dễ dàng xử lý dữ liệu trong bộ nhớ bằng cách sử dụng lớp dữ liệu, nhưng khi nói đến việc duy trì dữ liệu, bạn cần chuyển đổi dữ liệu này thành định dạng tương thích với dung lượng lưu trữ cơ sở dữ liệu. Để làm như vậy, bạn cần các bảng để lưu trữ dữ liệu và truy vấn để truy cập và sửa đổi dữ liệu.
Ba thành phần sau của Room giúp cho các quy trình này diễn ra liền mạch.
- Thực thể Room đại diện cho bảng trong cơ sở dữ liệu của ứng dụng. Bạn dùng các thực thể này để cập nhật dữ liệu được lưu trữ trong các hàng trong bảng và nhằm tạo hàng mới để chèn.
- DAO (đối tượng truy cập dữ liệu) của Room cung cấp các phương thức mà ứng dụng của bạn sử dụng để truy xuất, cập nhật, chèn và xoá dữ liệu trong cơ sở dữ liệu.
- Lớp cơ sở dữ liệu Room là lớp cơ sở dữ liệu cung cấp cho ứng dụng của bạn các phiên bản DAO được liên kết với cơ sở dữ liệu đó.
Bạn sẽ triển khai và tìm hiểu thêm về các thành phần này trong phần sau của lớp học lập trình này. Sơ đồ dưới đây minh hoạ cách các thành phần của Room hoạt động cùng nhau để tương tác với cơ sở dữ liệu.
Thêm phần phụ thuộc Room
Trong nhiệm vụ này, bạn sẽ thêm các thư viện thành phần Room cần có vào tệp Gradle.
- Mở tệp gradle cấp mô-đun
build.gradle.kts (Module: InventoryApp.app)
. - Trong khối
dependencies
, hãy thêm các phần phụ thuộc cho thư viện Room như minh hoạ trong mã sau.
//Room
implementation("androidx.room:room-runtime:${rootProject.extra["room_version"]}")
ksp("androidx.room:room-compiler:${rootProject.extra["room_version"]}")
implementation("androidx.room:room-ktx:${rootProject.extra["room_version"]}")
KSP là một API mạnh mẽ và đơn giản để phân tích cú pháp các chú giải Kotlin.
5. Tạo Thực thể (Entity) cho mặt hàng
Lớp Thực thể (Entity) định nghĩa một bảng và mỗi phiên bản thể hiện của lớp này đại diện cho một hàng trong bảng cơ sở dữ liệu. Lớp thực thể có các mục ánh xạ để cho Room biết cách lớp này định trình bày và tương tác với thông tin trong cơ sở dữ liệu. Trong ứng dụng của bạn, lớp thực thể này nắm giữ thông tin về các mặt hàng tồn kho, chẳng hạn như tên mặt hàng, giá mặt hàng và số lượng mặt hàng hiện có.
Thẻ chú giải @Entity
đánh dấu một lớp là lớp Thực thể (Entity) trong cơ sở dữ liệu. Đối với mỗi lớp Thực thể, ứng dụng sẽ tạo một bảng cơ sở dữ liệu để lưu các mặt hàng. Mỗi trường của Thực thể được biểu thị dưới dạng một cột trong cơ sở dữ liệu, trừ phi trường đó được thể hiện dưới dạng khác (hãy xem tài liệu về Thực thể để biết thông tin chi tiết). Mọi phiên bản của thực thể được lưu trữ trong cơ sở dữ liệu phải có một khoá chính. Khoá chính (primary key) được dùng để định nghĩa riêng biệt bản ghi/mục nhập trong bảng cơ sở dữ liệu của bạn. Khoá chính đại diện cho đối tượng thực thể, miễn là đối tượng đó tồn tại trong cơ sở dữ liệu. Sau khi ứng dụng chỉ định một khoá chính, bạn sẽ không thể sửa đổi khoá này.
Trong nhiệm vụ này, bạn sẽ tạo một lớp Thực thể và định nghĩa các trường để lưu trữ thông tin kho hàng sau đây đối với từng mặt hàng: Int
để lưu trữ khoá chính, String
để lưu trữ tên mặt hàng, double
để lưu trữ giá của mặt hàng và một Int
để lưu trữ số lượng trong kho.
- Mở đoạn mã khởi đầu trong Android Studio.
- Mở gói
data
trong gói cơ sởcom.example.inventory
. - Bên trong gói
data
, hãy mở lớp KotlinItem
, đại diện cho thực thể cơ sở dữ liệu trong ứng dụng.
// No need to copy over, this is part of the starter code
class Item(
val id: Int,
val name: String,
val price: Double,
val quantity: Int
)
Lớp dữ liệu
Các lớp dữ liệu chủ yếu được dùng để lưu giữ dữ liệu trong Kotlin. Các lớp này được định nghĩa bằng từ khoá data
. Các đối tượng lớp dữ liệu trong Kotlin cung cấp thêm cho bạn một số lợi ích. Ví dụ: trình biên dịch tự động tạo ra các tiện ích để so sánh, in và sao chép, chẳng hạn như toString()
, copy()
và equals()
.
Ví dụ:
// Example data class with 2 properties.
data class User(val firstName: String, val lastName: String){
}
Để đảm bảo tính nhất quán và hành vi có ý nghĩa của mã được tạo, các lớp dữ liệu phải đáp ứng những yêu cầu sau:
- Hàm khởi tạo chính phải có ít nhất một tham số.
- Tất cả tham số của hàm khởi tạo chính phải là
val
hoặcvar
. - Các lớp dữ liệu không được có trạng thái
abstract
,open
, hoặcsealed
.
Để tìm hiểu thêm về các lớp Dữ liệu (Data), hãy xem tài liệu về Lớp dữ liệu.
- Thêm tiền tố cho phần định nghĩa lớp
Item
vào từ khoádata
để chuyển đổi lớp đó thành một lớp dữ liệu.
data class Item(
val id: Int,
val name: String,
val price: Double,
val quantity: Int
)
- Phía trên phần khai báo cho lớp
Item
, hãy chú giải lớp dữ liệu bằng@Entity
. Sử dụng đối sốtableName
để thiết lậpitems
làm tên bảng SQLite.
import androidx.room.Entity
@Entity(tableName = "items")
data class Item(
...
)
- Chú giải thuộc tính
id
bằng@PrimaryKey
để thiết lậpid
làm khoá chính. Khoá chính là giá trị nhận dạng duy nhất cho mỗi bản ghi/mục nhập trong bảngItem
import androidx.room.PrimaryKey
@Entity(tableName = "items")
data class Item(
@PrimaryKey
val id: Int,
...
)
- Gán giá trị mặc định
0
choid
. Đây là giá trị cần thiết đểid
tự động tạo ra các giá trịid
. - Thêm tham số
autoGenerate
vào chú thích@PrimaryKey
để chỉ định xem liệu có có tự động tạo cột khoá chính hay không. Nếu bạn thiết lậpautoGenerate
thànhtrue
, Room sẽ tự động tạo một giá trị duy nhất đối với cột khoá chính khi một phiên bản thực thể mới được chèn vào cơ sở dữ liệu. Việc này giúp đảm bảo rằng mỗi phiên bản của thực thể đều có một giá trị nhận dạng duy nhất mà không cần chỉ định giá trị cho cột khoá chính theo cách thủ công
data class Item(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
// ...
)
Tuyệt vời! Bây giờ, sau khi tạo một lớp Thực thể, bạn có thể tạo một Đối tượng truy cập dữ liệu (DAO) để truy cập cơ sở dữ liệu.
6. Tạo đối tượng truy cập dữ liệu cho mặt hàng
Đối tượng truy cập dữ liệu (DAO) là một mẫu bạn có thể dùng để tách lớp cố định khỏi phần còn lại của ứng dụng bằng cách cung cấp một giao diện trừu tượng. Cách phân tách này tuân theo nguyên tắc trách nhiệm duy nhất mà bạn đã thấy trong các lớp học lập trình trước đây.
Chức năng của DAO là ẩn mọi chi tiết phức tạp liên quan đến việc thực hiện các thao tác cơ sở dữ liệu trong tầng cố định cơ sở khỏi phần còn lại của ứng dụng. Điều này cho phép bạn thay đổi lớp dữ liệu một cách độc lập với mã sử dụng dữ liệu đó.
Trong nhiệm vụ này, bạn sẽ khai báo DAO cho Room. DAO là các thành phần chính của Room, chịu trách nhiệm định nghĩa giao diện truy cập vào cơ sở dữ liệu.
DAO mà bạn tạo là một giao diện tuỳ chỉnh cung cấp các phương thức tiện lợi để truy vấn/truy xuất, chèn, xoá và cập nhật cơ sở dữ liệu. Room sẽ tạo mã phương thức triển khai của lớp này tại thời điểm biên dịch.
Thư viện Room
cung cấp cho bạn các chú giải tiện lợi như @Insert
, @Delete
và @Update
, để xác định các phương thức thực hiện thao tác chèn, xoá và cập nhật đơn giản mà không cần viết câu lệnh SQL.
Nếu bạn cần xác định các thao tác phức tạp hơn để chèn, xoá, cập nhật hoặc cần truy vấn dữ liệu trong cơ sở dữ liệu, hãy dùng chú giải @Query
.
Bên cạnh đó, khi bạn viết 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 ứng dụng Inventory (Kiểm kho), bạn cần tính năng giúp thực hiện những việc sau:
- Chèn hoặc thêm mặt hàng mới.
- Cập nhật mặt hàng hiện có để thay đổi tên, giá và số lượng.
- Tìm một mặt hàng cụ thể dựa trên khoá chính của mặt hàng đó (
id
). - Tìm tất cả mặt hàng để bạn có thể cho hiện các mặt hàng đó.
- Xoá một mục trong cơ sở dữ liệu.
Hãy hoàn tất các bước sau để triển khai DAO của mặt hàng trong ứng dụng:
- Trong gói
data
, hãy tạo giao diện KotlinItemDao.kt
.
- Chú giải giao diện
ItemDao
bằng@Dao
.
import androidx.room.Dao
@Dao
interface ItemDao {
}
- 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 phiên bản thể hiện của lớpEntity
củaitem
làm đối số. - Đánh dấu hàm này bằng từ khoá
suspend
để chạy trên một luồng riêng.
Có thể mất nhiều thời gian để thực thi các thao tác với cơ sở dữ liệu, nên sẽ cần phải chạy trên một luồng riêng. Room không cho phép truy cập cơ sở dữ liệu trên luồng chính.
import androidx.room.Insert
@Insert
suspend fun insert(item: Item)
Khi chèn các mặt hàng vào cơ sở dữ liệu, xung đột có thể xảy ra. Ví dụ: nhiều vị trí trong mã cố gắng cập nhật thực thể bằng các giá trị khác biệt và xung đột với nhau, chẳng hạn như cùng một khoá chính. Thực thể là một hàng trong DB. Trong ứng dụng Inventory (Kiểm kho), chúng ta chỉ chèn thực thể từ một nơi là màn hình Add Item (Thêm mặt hàng) nên dự kiến sẽ không gặp bất kỳ xung đột nào và có thể đặt chiến lược xung đột thành Ignore (Bỏ qua).
- Thêm một đối số
onConflict
và gán giá trịOnConflictStrategy.
IGNORE
cho đối số đó.
Đối số onConflict
cho Room biết việc cần làm trong trường hợp có xung đột. Chiến lược OnConflictStrategy.
IGNORE
sẽ bỏ qua một mục mới.
Để tìm hiểu thêm về các chiến lược có thể gây xung đột, hãy xem tài liệu OnConflictStrategy
này.
import androidx.room.OnConflictStrategy
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(item: Item)
Giờ đây, Room
sẽ tạo tất cả mã cần thiết để chèn item
vào cơ sở dữ liệu. Khi bạn gọi bất cứ hàm DAO nào được đánh dấu bằng chú thích Room, Room sẽ thực thi truy vấn SQL tương ứng trên cơ sở dữ liệu. Ví dụ: khi bạn gọi phương thức trên, insert()
từ 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.
- Thêm một hàm mới có chú thích
@Update
. Hàm này sẽ lấyItem
làm tham số.
Thực thể được cập nhật 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ể.
- Tương tự như phương thức
insert()
, hãy đánh dấu hàm này bằng từ khoásuspend
.
import androidx.room.Update
@Update
suspend fun update(item: Item)
Thêm một hàm khác có chú thích @Delete
để xoá (các) mục và thiết lập hàm đó thành hàm tạm ngưng.
import androidx.room.Delete
@Delete
suspend fun delete(item: Item)
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.
- Viết một truy vấn SQLite để truy xuất một mặt hàng cụ thể qua bảng mặt hàng dựa trên
id
đã cho. Đoạn mã sau đây cung cấp cho bạn một truy vấn mẫu chọn tất cả các cột từitems
, trong đóid
khớp với một giá trị cụ thể vàid
là một giá trị nhận dạng riêng biệt.
Ví dụ:
// Example, no need to copy over
SELECT * from items WHERE id = 1
- Thêm chú giải
@Query
. - Sử dụng truy vấn SQLite ở bước trước dưới dạng tham số chuỗi cho chú giải
@Query
. - Thêm tham số
String
vào@Query
. Đây là truy vấn SQLite để truy xuất một mặt hàng qua bảng danh sách mặt hàng.
Truy vấn hiện cho biết rằng sẽ chọn tất cả các cột từ items
, trong đó id
khớp với đối số :id
. Hãy lưu ý rằng :id
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.
@Query("SELECT * from items WHERE id = :id")
- Sau chú thích
@Query
, hãy thêm hàmgetItem()
(nhận đối sốInt
và trả vềFlow<Item>
).
import androidx.room.Query
import kotlinx.coroutines.flow.Flow
@Query("SELECT * from items WHERE id = :id")
fun getItem(id: Int): Flow<Item>
Bạn nên sử dụng Flow
trong tầng cố định. Với loại dữ liệu trả về là Flow
, bạn sẽ nhận được thông báo mỗi khi dữ liệu trong cơ sở dữ liệu thay đổi. Room
giúp Flow
này luôn cập nhật, nghĩa là bạn chỉ cần định nghĩa rõ ràng dữ liệu một lần. Chế độ thiết lập này rất hữu ích khi cập nhật danh sách hàng tồn kho mà bạn sẽ triển khai trong lớp học lập trình tiếp theo. Do loại dữ liệu trả về Flow
nên Room cũng chạy truy vấn trên luồng trong nền. Bạn không cần phải chỉ định rõ ràng thành hàm suspend
và gọi trong phạm vi coroutine.
- Thêm
@Query
có hàmgetAllItems()
: - Yêu cầu truy vấn SQLite trả về tất cả cột trong bảng
item
, theo thứ tự tăng dần. - Yêu cầu
getAllItems()
trả về danh sách các thực thểItem
dưới dạngFlow
.Room
giúpFlow
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.
@Query("SELECT * from items ORDER BY name ASC")
fun getAllItems(): Flow<List<Item>>
ItemDao
sau khi hoàn thành:
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update
import kotlinx.coroutines.flow.Flow
@Dao
interface ItemDao {
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(item: Item)
@Update
suspend fun update(item: Item)
@Delete
suspend fun delete(item: Item)
@Query("SELECT * from items WHERE id = :id")
fun getItem(id: Int): Flow<Item>
@Query("SELECT * from items ORDER BY name ASC")
fun getAllItems(): Flow<List<Item>>
}
- Dù không nhận thấy thay đổi nào, nhưng hãy tạo bản dựng cho ứng dụng của bạn để đảm bảo ứng dụng không có lỗi.
7. Tạo phiên bản thể hiện Database
Trong nhiệm vụ này, bạn tạo một RoomDatabase
sử dụng Entity
và DAO từ các nhiệm vụ trước đó. Lớp cơ sở dữ liệu định nghĩa danh sách các thực thể và DAO.
Lớp Database
cung cấp cho ứng dụng các phiên bản thể hiện (instance) của các DAO mà bạn định nghĩa. Đổi lại, ứng dụng có thể dùng các DAO đó để truy xuất dữ liệu từ cơ sở dữ liệu dưới dạng phiên bản thể hiện của đối tượng thực thể dữ liệu được liên kết. Ứng dụng cũng có thể dùng các thực thể dữ liệu đã định nghĩa để cập nhật các hàng trong bảng tương ứng hoặc tạo hàng mới để chèn dữ liệu.
Bạn cần tạo một lớp RoomDatabase
trừu tượng và chú thích lớp đó bằng @Database
. Lớp này có một phương thức trả về phiên bản hiện có của RoomDatabase
nếu cơ sở dữ liệu không tồn tại.
Dưới đây là quy trình chung để tải phiên bản thể hiện RoomDatabase
:
- Tạo lớp
public abstract
, lớp này mở rộngRoomDatabase
. Lớp trừu tượng mới mà bạn đã định nghĩa hoạt động như một lớp lưu giữ cơ sở dữ liệu. Lớp mà bạn đã định nghĩa là lớp trừu tượng vìRoom
tạo phương thức triển khai cho bạn. - Chú giải lớp này bằng
@Database
. Trong các đối số, hãy liệt kê các thực thể cho cơ sở dữ liệu và đặt số hiệu phiên bản. - Định nghĩa phương thức hoặc thuộc tính trừu tượng sẽ trả về phiên bản thể hiện
ItemDao
, vàRoom
sẽ tạo phương thức triển khai cho bạn. - Bạn chỉ cần một phiên bản thể hiện của
RoomDatabase
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
Room.databaseBuilder
củaRoom
để tạo cơ sở dữ liệu (item_database
) 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ó.
Tạo Cơ sở dữ liệu
- Trong gói
data
, hãy tạo một lớp Kotlin làInventoryDatabase.kt
. - Trong tệp
InventoryDatabase.kt
, hãy thiết lập lớpInventoryDatabase
làm lớpabstract
, lớp này mở rộngRoomDatabase
. - Chú giải lớp này bằng
@Database
. Bỏ qua lỗi thiếu tham số, bạn sẽ khắc phục lỗi này trong bước tiếp theo.
import androidx.room.Database
import androidx.room.RoomDatabase
@Database
abstract class InventoryDatabase : RoomDatabase() {}
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.
- Hãy chỉ định
Item
làm lớp duy nhất có danh sáchentities
. - Thiết lập
version
thành1
. Bạn phải tăng số hiệu phiên bản mỗi lần thay đổi giản đồ của bảng cơ sở dữ liệu. - 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 đồ.
@Database(entities = [Item::class], version = 1, exportSchema = false)
- Bên trong phần nội dung của lớp, hãy khai báo hàm trừu tượng trả về
ItemDao
để cơ sở dữ liệu biết về DAO.
abstract fun itemDao(): ItemDao
- Bên dưới hàm trừu tượng này, hãy định nghĩa
companion object
, cho phép truy cập vào các phương thức để tạo hoặc lấy cơ sở dữ liệu và sử dụng tên lớp làm bộ hạn định.
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ến Instance
lưu giữ một 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 đảm bảo chỉ một phiên bản thể hiện của cơ sở dữ liệu được mở tại một thời điểm nhất định, vì cơ sở dữ liệu là một tài nguyên khó tạo và duy trì.
- Chú giải
Instance
bằng@Volatile
.
Giá trị của biến volatile không bao giờ được lưu vào bộ nhớ đệm và tất cả lượt đọc và ghi đều đến và đi từ bộ nhớ chính. Các tính năng này giúp đảm bảo giá trị của Instance
luôn cập nhật và giống nhau đối với 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.
@Volatile
private var Instance: InventoryDatabase? = null
- Bên dưới
Instance
, vẫn bên trong đối tượngcompanion
, hãy khai báo một phương thứcgetDatabase()
có tham sốContext
mà hàm tạo cơ sở dữ liệu sẽ cần đến. - Trả về một kiểu
InventoryDatabase
. Sẽ xuất hiện một thông báo lỗi vìgetDatabase()
chưa trả về giá trị nào.
import android.content.Context
fun getDatabase(context: Context): InventoryDatabase {}
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 được gọi là tình huống tương tranh (race condition). Việc bao bọc mã để đưa cơ sở dữ liệu vào bên trong khối synchronized
đồ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. Hãy sử dụng khối synchronized{}
để tránh tình huống tương tranh.
- Bên trong
getDatabase()
, hãy trả về biếnInstance
, hoặc nếuInstance
rỗng, hãy khởi tạo biến đó bên trong khốisynchronized{}
. Hãy dùng toán tử elvis (?:
) để thực hiện thao tác này. - Truyền vào đối tượng đồng hành
this
. Bạn sẽ sửa lỗi trong các bước sau.
return Instance ?: synchronized(this) { }
- Bên trong khối đã đồng bộ hoá, hãy sử dụng trình tạo cơ sở dữ liệu để tải cơ sở dữ liệu. Hãy tiếp tục bỏ qua các lỗi, bạn sẽ khắc phục trong các bước tiếp theo.
import androidx.room.Room
Room.databaseBuilder()
- Bên trong khối
synchronized
, hãy sử dụng trình tạo cơ sở dữ liệu để tải cơ sở dữ liệu. Truyền ngữ cảnh ứng dụng, lớp cơ sở dữ liệu và tên cho cơ sở dữ liệu (item_database
) vàoRoom.databaseBuilder()
.
Room.databaseBuilder(context, InventoryDatabase::class.java, "item_database")
Android Studio sẽ tạo thông báo lỗi Kiểu không khớp (Type Mismatch). Để xoá lỗi này, bạn phải thêm build()
trong các bước sau.
- Thêm chiến lược di chuyển cần có vào hàm tạo. Sử dụng
.
fallbackToDestructiveMigration()
.
.fallbackToDestructiveMigration()
- Để tạo phiên bản thể hiện của cơ sở dữ liệu, hãy gọi
.build()
. Lệnh gọi này sẽ xoá các thông báo lỗi của Android Studio.
.build()
- Sau
build()
, hãy thêm một khốialso
và gánInstance = it
để giữ lại thông tin tham chiếu đến phiên bản thể hiện db mới tạo gần đây.
.also { Instance = it }
- Ở cuối khối
synchronized
, hãy trả vềinstance
. Mã hoàn chỉnh của bạn sẽ có dạng như sau:
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
/**
* Database class with a singleton Instance object.
*/
@Database(entities = [Item::class], version = 1, exportSchema = false)
abstract class InventoryDatabase : RoomDatabase() {
abstract fun itemDao(): ItemDao
companion object {
@Volatile
private var Instance: InventoryDatabase? = null
fun getDatabase(context: Context): InventoryDatabase {
// if the Instance is not null, return it, otherwise create a new database instance.
return Instance ?: synchronized(this) {
Room.databaseBuilder(context, InventoryDatabase::class.java, "item_database")
.build()
.also { Instance = it }
}
}
}
}
- Tạo bản dựng mã của bạn để đảm bảo không có lỗi.
8. Triển khai Repository (Kho lưu trữ)
Trong nhiệm vụ này, bạn sẽ triển khai giao diện ItemsRepository
và lớp OfflineItemsRepository
để cung cấp các thực thể get
, insert
, delete
và update
từ cơ sở dữ liệu.
- Mở tệp
ItemsRepository.kt
trong góidata
. - Thêm các hàm sau vào giao diện, (được liên kết với phương thức triển khai DAO).
import kotlinx.coroutines.flow.Flow
/**
* Repository that provides insert, update, delete, and retrieve of [Item] from a given data source.
*/
interface ItemsRepository {
/**
* Retrieve all the items from the the given data source.
*/
fun getAllItemsStream(): Flow<List<Item>>
/**
* Retrieve an item from the given data source that matches with the [id].
*/
fun getItemStream(id: Int): Flow<Item?>
/**
* Insert item in the data source
*/
suspend fun insertItem(item: Item)
/**
* Delete item from the data source
*/
suspend fun deleteItem(item: Item)
/**
* Update item in the data source
*/
suspend fun updateItem(item: Item)
}
- Mở tệp
OfflineItemsRepository.kt
trong góidata
. - Truyền vào một tham số hàm khởi tạo thuộc kiểu
ItemDao
.
class OfflineItemsRepository(private val itemDao: ItemDao) : ItemsRepository
- Trong lớp
OfflineItemsRepository
, hãy ghi đè các hàm được định nghĩa trong giao diệnItemsRepository
và gọi các hàm tương ứng từItemDao
.
import kotlinx.coroutines.flow.Flow
class OfflineItemsRepository(private val itemDao: ItemDao) : ItemsRepository {
override fun getAllItemsStream(): Flow<List<Item>> = itemDao.getAllItems()
override fun getItemStream(id: Int): Flow<Item?> = itemDao.getItem(id)
override suspend fun insertItem(item: Item) = itemDao.insert(item)
override suspend fun deleteItem(item: Item) = itemDao.delete(item)
override suspend fun updateItem(item: Item) = itemDao.update(item)
}
Triển khai lớp AppContainer
Trong nhiệm vụ này, bạn sẽ tạo thực thể cơ sở dữ liệu và truyền phiên bản thể hiện DAO đến lớp OfflineItemsRepository
.
- Mở tệp
AppContainer.kt
trong góidata
. - Truyền thực thể
ItemDao()
vào hàm khởi tạoOfflineItemsRepository
. - Tạo phiên bản thể hiện cơ sở dữ liệu bằng cách gọi
getDatabase()
trên lớpInventoryDatabase
, truyền ngữ cảnh và gọi.itemDao()
để tạo phiên bản thể hiệnDao
.
override val itemsRepository: ItemsRepository by lazy {
OfflineItemsRepository(InventoryDatabase.getDatabase(context).itemDao())
}
Hiện bạn đã có tất cả yếu tố nền móng để làm việc với Room. Mã này sẽ biên dịch và chạy được, 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 để kiểm tra cơ sở dữ liệu của bạn. Để hoàn tất quy trình kiểm thử, bạn cần có ViewModel
để trao đổi với cơ sở dữ liệu.
9. Thêm chức năng lưu
Đến nay, bạn đã tạo một cơ sở dữ liệu và các lớp trên giao diện người dùng là một phần của mã khởi động. Để lưu dữ liệu tạm thời của ứng dụng và truy cập cơ sở dữ liệu, bạn cần cập nhật các ViewModel
. ViewModel
của bạn tương tác với cơ sở dữ liệu thông qua DAO và cung cấp dữ liệu cho giao diện người dùng. Mọi hoạt động liên quan đến cơ sở dữ liệu đều cần chạy khỏi luồng giao diện người dùng chính. Bạn sẽ thực hiện việc này bằng cách sử dụng coroutine và viewModelScope
.
Hướng dẫn từng bước về lớp trạng thái giao diện người dùng
Mở tệp ui/item/ItemEntryViewModel.kt
. Lớp dữ liệu ItemUiState
thể hiện trạng thái giao diện người dùng của một Mục. Lớp dữ liệu ItemDetails
biểu thị một mục.
Mã khởi đầu cung cấp cho bạn 3 hàm mở rộng:
- Hàm mở rộng
ItemDetails.toItem()
chuyển đổi đối tượng trạng thái giao diện người dùngItemUiState
thành kiểu thực thểItem
. - Hàm mở rộng
Item.toItemUiState()
chuyển đổi đối tượng thực thể RoomItem
thành kiểu trạng thái giao diện người dùngItemUiState
. - Hàm mở rộng
Item.toItemDetails()
chuyển đổi đối tượng thực thể RoomItem
thànhItemDetails
.
// No need to copy, this is part of starter code
/**
* Represents Ui State for an Item.
*/
data class ItemUiState(
val itemDetails: ItemDetails = ItemDetails(),
val isEntryValid: Boolean = false
)
data class ItemDetails(
val id: Int = 0,
val name: String = "",
val price: String = "",
val quantity: String = "",
)
/**
* Extension function to convert [ItemDetails] to [Item]. If the value of [ItemDetails.price] is
* not a valid [Double], then the price will be set to 0.0. Similarly if the value of
* [ItemDetails.quantity] is not a valid [Int], then the quantity will be set to 0
*/
fun ItemDetails.toItem(): Item = Item(
id = id,
name = name,
price = price.toDoubleOrNull() ?: 0.0,
quantity = quantity.toIntOrNull() ?: 0
)
fun Item.formatedPrice(): String {
return NumberFormat.getCurrencyInstance().format(price)
}
/**
* Extension function to convert [Item] to [ItemUiState]
*/
fun Item.toItemUiState(isEntryValid: Boolean = false): ItemUiState = ItemUiState(
itemDetails = this.toItemDetails(),
isEntryValid = isEntryValid
)
/**
* Extension function to convert [Item] to [ItemDetails]
*/
fun Item.toItemDetails(): ItemDetails = ItemDetails(
id = id,
name = name,
price = price.toString(),
quantity = quantity.toString()
)
Bạn sẽ sử dụng lớp trên trong các mô hình hiển thị để đọc và cập nhật giao diện người dùng.
Cập nhật ViewModel của ItemEntry
Trong nhiệm vụ này, bạn sẽ truyền từ kho lưu trữ vào tệp ItemEntryViewModel.kt
. Bạn cũng sẽ lưu thông tin chi tiết về mặt hàng đã nhập trong màn hình Add Item (Thêm mặt hàng) vào cơ sở dữ liệu.
- Hãy chú ý đến hàm riêng tư
validateInput()
trong lớpItemEntryViewModel
.
// No need to copy over, this is part of starter code
private fun validateInput(uiState: ItemDetails = itemUiState.itemDetails): Boolean {
return with(uiState) {
name.isNotBlank() && price.isNotBlank() && quantity.isNotBlank()
}
}
Hàm trên kiểm tra xem name
, price
và quantity
có trống hay không. Bạn sẽ dùng hàm này để xác minh hoạt động đầu vào của người dùng, trước khi thêm hoặc cập nhật thực thể trong cơ sở dữ liệu.
- Mở lớp
ItemEntryViewModel
và thêm tham số hàm khởi tạo mặc địnhprivate
thuộc kiểuItemsRepository
.
import com.example.inventory.data.ItemsRepository
class ItemEntryViewModel(private val itemsRepository: ItemsRepository) : ViewModel() {
}
- Cập nhật
initializer
cho mô hình thành phần hiển thị mục nhập trongui/AppViewModelProvider.kt
và truyền vào thực thể lưu trữ dưới dạng tham số.
object AppViewModelProvider {
val Factory = viewModelFactory {
// Other Initializers
// Initializer for ItemEntryViewModel
initializer {
ItemEntryViewModel(inventoryApplication().container.itemsRepository)
}
//...
}
}
- Chuyển đến tệp
ItemEntryViewModel.kt
và ở cuối lớpItemEntryViewModel
, sau đó thêm hàm tạm ngưng có tênsaveItem()
để chèn một mục vào cơ sở dữ liệu Room. Hàm này thêm dữ liệu vào cơ sở dữ liệu theo cách không chặn luồng thực thi.
suspend fun saveItem() {
}
- Bên trong hàm này, hãy kiểm tra tính hợp lệ của
itemUiState
, và chuyển đổi thànhItem
để Room có thể hiểu được dữ liệu. - Gọi
insertItem()
trênitemsRepository
rồi truyền dữ liệu vào. Giao diện người dùng gọi hàm này để thêm thông tin chi tiết về Item (mặt hàng) vào cơ sở dữ liệu.
suspend fun saveItem() {
if (validateInput()) {
itemsRepository.insertItem(itemUiState.itemDetails.toItem())
}
}
Hiện bạn đã thêm tất cả hàm cần thiết để thêm đối tượng vào cơ sở dữ liệu. Trong nhiệm vụ tiếp theo, bạn sẽ cập nhật giao diện người dùng để sử dụng các hàm trên.
Hướng dẫn từng bước về thành phần kết hợp ItemEntryBody()
- Trong tệp
ui/item/ItemEntryScreen.kt
, thành phần kết hợpItemEntryBody()
được triển khai một phần cho bạn dưới dạng một phần của mã khởi đầu. Hãy xem thành phần kết hợpItemEntryBody()
trong lệnh gọi hàmItemEntryScreen()
.
// No need to copy over, part of the starter code
ItemEntryBody(
itemUiState = viewModel.itemUiState,
onItemValueChange = viewModel::updateUiState,
onSaveClick = { },
modifier = Modifier
.padding(innerPadding)
.verticalScroll(rememberScrollState())
.fillMaxWidth()
)
- Lưu ý trạng thái giao diện người dùng và hàm lambda
updateUiState
đang được truyền dưới dạng tham số hàm. Xem xét phần định nghĩa hàm để thấy trạng thái của giao diện người dùng đang được cập nhật.
// No need to copy over, part of the starter code
@Composable
fun ItemEntryBody(
itemUiState: ItemUiState,
onItemValueChange: (ItemUiState) -> Unit,
onSaveClick: () -> Unit,
modifier: Modifier = Modifier
) {
Column(
// ...
) {
ItemInputForm(
itemDetails = itemUiState.itemDetails,
onValueChange = onItemValueChange,
modifier = Modifier.fillMaxWidth()
)
Button(
onClick = onSaveClick,
enabled = itemUiState.isEntryValid,
shape = MaterialTheme.shapes.small,
modifier = Modifier.fillMaxWidth()
) {
Text(text = stringResource(R.string.save_action))
}
}
}
Bạn đang hiển thị ItemInputForm
và nút Save (Lưu) trong thành phần kết hợp này. Trong thành phần kết hợp ItemInputForm()
, bạn sẽ hiển thị 3 trường văn bản. Tính năng Lưu chỉ được bật nếu bạn nhập văn bản vào các trường văn bản. Giá trị isEntryValid
là true nếu văn bản trong tất cả các trường văn bản là hợp lệ (không trống).
- Hãy xem phương thức triển khai hàm có khả năng kết hợp
ItemInputForm()
và chú ý đến tham số hàmonValueChange
. Bạn đang cập nhật giá trịitemDetails
bằng giá trị do người dùng nhập vào các trường văn bản. Vào thời điểm bật nút Save (Lưu),itemUiState.itemDetails
sẽ có các giá trị cần lưu.
// No need to copy over, part of the starter code
@Composable
fun ItemEntryBody(
//...
) {
Column(
// ...
) {
ItemInputForm(
itemDetails = itemUiState.itemDetails,
//...
)
//...
}
}
// No need to copy over, part of the starter code
@Composable
fun ItemInputForm(
itemDetails: ItemDetails,
modifier: Modifier = Modifier,
onValueChange: (ItemUiState) -> Unit = {},
enabled: Boolean = true
) {
Column(modifier = modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(16.dp)) {
OutlinedTextField(
value = itemUiState.name,
onValueChange = { onValueChange(itemDetails.copy(name = it)) },
//...
)
OutlinedTextField(
value = itemUiState.price,
onValueChange = { onValueChange(itemDetails.copy(price = it)) },
//...
)
OutlinedTextField(
value = itemUiState.quantity,
onValueChange = { onValueChange(itemDetails.copy(quantity = it)) },
//...
)
}
}
Thêm trình nghe lượt nhấp vào nút Save (Lưu)
Để liên kết mọi thứ với nhau, hãy thêm một trình xử lý lượt nhấp vào nút Save (Lưu). Bên trong trình xử lý lượt nhấp, hãy khởi chạy một coroutine rồi gọi saveItem()
để lưu dữ liệu trong cơ sở dữ liệu Room.
- Trong
ItemEntryScreen.kt
, bên trong hàm hàm có khả năng kết hợpItemEntryScreen
, hãy tạo mộtval
có tên làcoroutineScope
bằng hàm có khả năng kết hợprememberCoroutineScope()
.
import androidx.compose.runtime.rememberCoroutineScope
val coroutineScope = rememberCoroutineScope()
- Cập nhật lệnh gọi hàm
ItemEntryBody
()
và chạy một coroutine bên trong hàm lambdaonSaveClick
.
ItemEntryBody(
// ...
onSaveClick = {
coroutineScope.launch {
}
},
modifier = modifier.padding(innerPadding)
)
- Xem xét phương thức triển khai hàm
saveItem()
trong tệpItemEntryViewModel.kt
để kiểm tra xemitemUiState
có hợp lệ hay không, chuyển đổiitemUiState
thành loạiItem
và chèn tệp đó vào cơ sở dữ liệu bằngitemsRepository.insertItem()
.
// No need to copy over, you have already implemented this as part of the Room implementation
suspend fun saveItem() {
if (validateInput()) {
itemsRepository.insertItem(itemUiState.itemDetails.toItem())
}
}
- Trong
ItemEntryScreen.kt
, bên trong hàm có khả năng kết hợpItemEntryScreen
, bên trong coroutine, hãy gọiviewModel.saveItem()
để lưu mục trong cơ sở dữ liệu.
ItemEntryBody(
// ...
onSaveClick = {
coroutineScope.launch {
viewModel.saveItem()
}
},
//...
)
Xin lưu ý rằng bạn không sử dụng viewModelScope.launch()
cho saveItem()
trong tệp ItemEntryViewModel.kt
, nhưng điều này cần thiết cho ItemEntryBody
()
khi gọi phương thức kho lưu trữ. Bạn chỉ có thể gọi các hàm tạm ngưng từ một coroutine hoặc một hàm tạm ngưng khác. Hàm viewModel.saveItem()
là hàm tạm ngưng.
- Tạo bản dựng và chạy ứng dụng của bạn.
- Nhấn vào nút hành động nổi +.
- Trong màn hình Add Item (Thêm mặt hàng), hãy thêm thông tin chi tiết về mặt hàng rồi nhấn vào Save (Lưu). Lưu ý rằng thao tác nhấn vào nút Save (Lưu) không đóng màn hình Add Item (Thêm mặt hàng).
- Trong hàm lambda
onSaveClick
, hãy thêm một lệnh gọi tớinavigateBack()
sau lệnh gọi đếnviewModel.saveItem()
để quay lại màn hình trước đó. HàmItemEntryBody()
của bạn sẽ có dạng như mã sau:
ItemEntryBody(
itemUiState = viewModel.itemUiState,
onItemValueChange = viewModel::updateUiState,
onSaveClick = {
coroutineScope.launch {
viewModel.saveItem()
navigateBack()
}
},
modifier = modifier.padding(innerPadding)
)
- Chạy lại ứng dụng và thực hiện các bước tương tự để nhập và lưu dữ liệu. Lần này, hãy lưu ý rằng ứng dụng sẽ quay lại màn hình Inventory (Kho hàng).
Thao tác này sẽ lưu dữ liệu, nhưng bạn không thể xem dữ liệu kho hàng trong ứng dụng. Trong nhiệm vụ tiếp theo, bạn sẽ sử dụng Trình kiểm tra cơ sở dữ liệu để xem dữ liệu đã lưu.
10. Dùng Trình kiểm tra cơ sở dữ liệu để xem nội dung của cơ sở dữ liệu
Trình kiểm tra cơ sở dữ liệu cho phép bạn kiểm tra, truy vấn và sửa đổi cơ sở dữ liệu của ứng dụng trong khi ứng dụng chạy. Điều này đặc biệt hữu ích khi gỡ lỗi cơ sở dữ liệu. Trình kiểm tra cơ sở dữ liệu hoạt động với SQLite thuần tuý và với các thư viện được xây dựng dựa trên SQLite, chẳng hạn như Room. Trình kiểm tra cơ sở dữ liệu hoạt động hiệu quả nhất trên trình mô phỏng/thiết bị chạy API cấp 26.
- Chạy ứng dụng của bạn trên trình mô phỏng hoặc một thiết bị được kết nối chạy API cấp 26 trở lên (nếu bạn chưa thực hiện việc này).
- Trong Android Studio, trên thanh trình đơn, hãy chọn View (Xem) > Tool Windows (Cửa sổ công cụ) > App Inspection (Kiểm tra ứng dụng).
- Chọn thẻ Database Inspector (Trình kiểm tra cơ sở dữ liệu).
- Trong ngăn Database Inspector (Trình kiểm tra cơ sở dữ liệu), hãy chọn
com.example.inventory
trên trình đơn thả xuống (nếu chưa chọn). item_database trong ứng dụng Inventory (Kiểm kho) xuất hiện trong ngăn Databases (Cơ sở dữ liệu).
- Mở rộng nút cho item_database trong ngăn Databases (Cơ sở dữ liệu) rồi chọn Item (Mặt hàng) để kiểm tra. Nếu ngăn Databases (Cơ sở dữ liệu) trống, hãy sử dụng trình mô phỏng để thêm một số mặt hàng vào cơ sở dữ liệu bằng màn hình Add Item (Thêm mặt hàng).
- Chọn hộp đánh dấu Live updates (Thông tin cập nhật trực tiếp) trong Trình kiểm tra cơ sở dữ liệu để tự động cập nhật những dữ liệu xuất hiện khi bạn tương tác với ứng dụng đang chạy trong trình mô phỏng hoặc thiết bị.
Xin chúc mừng! Bạn đã sử dụng Room để tạo một ứng dụng có thể duy trì dữ liệu. Trong lớp học lập trình tiếp theo, bạn sẽ thêm lazyColumn
vào ứng dụng của mình để cho thấy các mặt hàng trên cơ sở dữ liệu và thêm tính năng mới vào ứng dụng, như khả năng xoá và cập nhật các thực thể. Hẹn gặp bạn ở lớp học này nhé!
11. Lấy đoạn mã giải pháp
Đoạn mã giải pháp cho lớp học lập trình này nằm trong kho lưu trữ GitHub. Để tải đoạn mã cho lớp học lập trình đã hoàn thành này, hãy sử dụng lệnh git sau:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-inventory-app.git $ cd basic-android-kotlin-compose-training-inventory-app $ git checkout room
Ngoài ra, bạn có thể tải kho lưu trữ xuống dưới dạng tệp zip rồi giải nén và mở trong Android Studio.
Nếu bạn muốn tham khảo đoạn mã giải pháp cho lớp học lập trình này, hãy xem trên GitHub.
12. Tóm tắt
- 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. - Sử dụng Trình kiểm tra cơ sở dữ liệu để xem dữ liệu được lưu trong cơ sở dữ liệu Android SQLite.
13. Tìm hiểu thêm
Tài liệu dành cho nhà phát triển Android
- Lưu dữ liệu trong cơ sở dữ liệu cục bộ bằng Room
- androidx.room
- Gỡ lỗi về cơ sở dữ liệu bằng Trình kiểm tra cơ sở dữ liệu
Bài đăng trên blog
Video
Các tài liệu và bài viết khác