1. Trước khi bắt đầu
Hầu hết ứng dụng hoàn thiện đều có dữ liệu cần lưu, 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 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. Đối với đa số các trường hợp như vậy, bạn sẽ sử dụng cơ sở dữ liệu để lưu trữ những dữ liệu cố định này.
Room là một thư viện về 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, định cấu hình và tương tác với cơ sở dữ liệu. Room cũng kiểm tra thời gian biên dịch của các câu lệnh SQLite.
Hình ảnh dưới đây cho thấy Room phù hợp với kiến trúc tổng thể được đề xuất trong khoá học này.
Điều kiện tiên quyết
- Bạn biết cách tạo giao diện người dùng (UI) cơ bản cho một ứng dụng Android.
- Bạn biết cách sử dụng hoạt động (activity), mảnh (fragment) và thành phần hiển thị (view).
- Bạn biết điều hướng giữa các mảnh bằng cách sử dụng Safe Args để truyền dữ liệu giữa các mảnh.
- Bạn đã quen thuộc với các thành phần cấu trúc Android gồm
ViewModel
,LiveData
vàFlow
, cũng như biết cách sử dụngViewModelProvider.Factory
để tạo ViewModel. - Bạn đã nắm rõ kiến thức cơ bản liên quan đến cơ chế xử lý đồng thời.
- Bạn biết cách sử dụng coroutine cho các tác vụ chạy trong thời gian dài.
- Bạn có kiến thức cơ bản về cơ sở dữ liệu SQL và ngôn ngữ SQLite.
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 và lớp 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.
Sản phẩm bạn sẽ tạo
- Bạn sẽ tạo một ứng dụng Kiểm kho (Inventory), 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ó
- Mã khởi động cho ứng dụng Inventory (Kiểm kho).
- Máy tính đã cài đặt Android Studio.
2. Tổng quan về ứng dụng
Trong lớp học lập trình này, bạn sẽ làm việc với một ứng dụng cơ bản gọi là ứng dụng Inventory, bạn sẽ thêm tầng cơ sở dữ liệu vào ứng dụng này bằng thư viện Room. Phiên bản cuối cùng của ứng dụng cho thấy danh sách các mặt hàng trong cơ sở dữ liệu kiểm kho bằng cách sử dụng RecyclerView
. Người dùng sẽ có quyền 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 kiểm kho (bạn sẽ hoàn thiện chức năng của ứng dụng trong lớp học lập trình tiếp theo).
Dưới đây là các ảnh chụp màn hình trong phiên bản cuối cùng của ứng dụng.
3. Tổng quan về ứng dụng khởi động
Tải mã khởi động cho lớp học lập trình này
Lớp học lập trình này sẽ cung cấp mã khởi động, cho phép bạn mở rộng bằng các tính năng được dạy trong lớp học lập trình này. Mã khởi động có thể chứa những đoạn mã đã quen thuộc với bạn từ các lớp học lập trình trước, đồng thời cũng có những đoạn mã còn lạ lẫm mà bạn sẽ tìm hiểu trong các lớp sau.
Nếu bạn sử dụng mã khởi động lấy trên GitHub, hãy lưu ý tên thư mục là android-basics-kotlin-inventory-app-starter
. Hãy chọn thư mục này khi bạn mở dự án trong Android Studio.
Để lấy mã cho lớp học lập trình này và mở mã đó trong Android Studio, hãy thực hiện các bước sau.
Lấy mã
- Nhấp vào URL được cung cấp. Thao tác này sẽ mở trang GitHub của dự án trong một trình duyệt.
- Trên trang GitHub của dự án, hãy nhấp vào nút Code (Mã), một hộp thoại sẽ xuất hiện.
- Trong hộp thoại này, hãy nhấp vào nút Download ZIP (Tải tệp ZIP xuống) để lưu dự án vào máy tính. Chờ quá trình tải xuống hoàn tất.
- Xác định vị trí của tệp trên máy tính (có thể trong thư mục Downloads (Tệp đã tải xuống)).
- Nhấp đúp vào tệp ZIP để giải nén. Thao tác này sẽ tạo một thư mục mới chứa các tệp dự án.
Mở dự án trong Android Studio
- Khởi động Android Studio.
- Trong cửa sổ Welcome to Android Studio (Chào mừng bạn đến với Android Studio), hãy nhấp vào Open an existing Android Studio project (Mở một dự án hiện có trong Android Studio).
Lưu ý: Nếu Android Studio đã mở sẵn thì thay vào đó, hãy chọn tuỳ chọn sau đây trong trình đơn File > New > Import Project (Tệp > Mới > Nhập dự án).
- Trong hộp thoại Import Project (Nhập dự án), hãy chuyển đến nơi chứa thư mục dự án đã giải nén (có thể trong thư mục Downloads (Tệp đã tải xuống)).
- Nhấp đúp vào thư mục dự án đó.
- Chờ Android Studio mở dự án.
- Nhấp vào nút Run (Chạy) để tạo và chạy ứng dụng. Đảm bảo ứng dụng được xây dựng như mong đợi.
- Duyệt qua các tệp dự án trong cửa sổ công cụ Project (Dự án) để xem cách ứng dụng được thiết lập.
Tổng quan về mã khởi động
- Mở dự án bằng mã khởi động 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 đang chạy API cấp 26 trở lên. 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.
- Ứng dụng không cho thấy dữ liệu kho hàng. Hãy để ý nút hành động nổi để thêm các mục mới vào cơ sở dữ liệu.
- Nhấp vào nút hành động nổi. Ứ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 đề liên quan đến mã khởi động
- 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ấn vào Save (Lưu). Mảnh của thao tác thêm mục mới chưa đóng. Quay lại bằng phím quay lại của hệ thống. Mặt hàng mới chưa được lưu và không được liệt kê trên màn hình thông tin kho hàng. 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 phần cơ sở dữ liệu của một ứng dụng có chức năng lưu thông tin kiểm kho 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ề mã
Mã khởi động bạn tải xuống có bố cục màn hình được thiết kế sẵn phù hợp với 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. Dưới đây là hướng dẫn ngắn gọn về một số tệp để bạn bắt đầu.
main_activity.xml
Hoạt động chính lưu trữ tất cả mảnh khác trong ứng dụng. Phương thức onCreate()
truy xuất NavController
từ NavHostFragment
và thiết lập thanh thao tác để sử dụng với NavController
.
item_list_fragment.xml
Màn hình đầu tiên xuất hiện trong ứng dụng, chủ yếu chứa RecyclerView và một nút hành động nổi. Bạn sẽ triển khai RecyclerView trong giai đoạn sau của lộ trình này.
fragment_add_item.xml
Bố cục này chứa các trường văn bản để nhập thông tin chi tiết về các mặt hàng tồn kho cần thêm.
ItemListFragment.kt
Mảnh này chủ yếu chứa mã nguyên mẫu. Trong phương thức onViewCreated()
, trình nghe lượt nhấp được đặt trên nút hành động nổi để chuyển đến mảnh thêm mục.
AddItemFragment.kt
Mảnh này dùng để thêm mục mới vào cơ sở dữ liệu. Hàm onCreateView()
khởi tạo biến binding và hàm onDestroyView()
ẩn bàn phím trước khi huỷ mảnh.
4. Các thành phần chính của Room
Kotlin đem lại một cách thức dễ dàng để xử lý dữ liệu thông qua các lớp dữ liệu (data class). Bạn có thể truy cập và đôi khi là sửa đổi được dữ liệu này 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ó bảng (table) và truy vấn (query) để truy cập và sửa đổi dữ liệu. Các thành phần sau của Room giúp cho các quy trình này diễn ra suôn sẻ.
Có 3 thành phần chính trong Room:
- Thực thể dữ liệu (data entity) đại diện cho bảng trong cơ sở dữ liệu của ứng dụng. Các thực thể này được dùng cho việc cập nhật dữ liệu lưu trên các hàng trong bảng và tạo hàng mới để chèn dữ liệu.
- Đối tượng truy cập dữ liệu (data access object – DAO) 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 (database class) lưu giữ cơ sở dữ liệu và là điểm truy cập chính cho kết nối cơ sở đến cơ sở dữ liệu của ứng dụng. Lớp cơ sở dữ liệu cung cấp cho ứng dụng của bạn các thực thể của DAO 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 thư viện 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 (Module: InventoryApp.app)
. Trong khốidependencies
, hãy thêm các phần phụ thuộc sau cho thư viện Room.
// Room implementation "androidx.room:room-runtime:$room_version" kapt "androidx.room:room-compiler:$room_version" implementation "androidx.room:room-ktx:$room_version"
5. Tạo thực thể cho mặt hàng
Lớp Thực thể (Entity) xác định 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, thực thể này sẽ giữ thông tin về các mặt hàng lưu kho như tên mặt hàng, giá mặt hàng và lượng hàng có sẵn.
Thẻ chú giải @Entity
đánh dấu một lớp là lớp Thực thể cơ sở dữ liệu. Một bảng cơ sở dữ liệu được tạo mỗi lớp Thực thể để 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 để xác định riêng biệt bản ghi/mục nhập trong bảng cơ sở dữ liệu của bạn. Sau khi gán, bạn sẽ không sửa được khoá chính vì khoá chính sẽ đại diện cho đối tượng thực thể trong suốt thời gian đối tượng này tồn tại trong cơ sở dữ liệu.
Trong nhiệm vụ này, bạn sẽ tạo một lớp Thực thể (Entity). Khai báo các trường trong lớp này để lưu trữ những thông tin lưu kho như sau cho mỗi 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.Int
để lưu trữ số lượng trong kho.
- Mở mã khởi động trong Android Studio.
- Tạo gói có tên là
data
trong gói cơ sởcom.example.inventory
.
- Trong gói
data
, hãy tạo một lớp Kotlin tên làItem
. Lớp này sẽ biểu thị cho một thực thể cơ sở dữ liệu trong ứng dụng của bạn. Trong bước tiếp theo, bạn sẽ thêm các trường tương ứng để lưu trữ thông tin tồn kho. - Hãy cập nhật khai báo lớp
Item
bằng mã như sau. Khai báoid
thuộc loạiInt
,itemName
thuộc loạiString,
itemPrice
thuộc loạiDouble
vàquantityInStock
thuộc loạiInt
làm tham số cho hàm khởi tạo chính. Gán giá trị mặc định từ0
đếnid
. Đây sẽ là 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
.
class Item(
val id: Int = 0,
val itemName: String,
val itemPrice: Double,
val quantityInStock: 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 dấu bằng từ khoá data
. Các đối tượng lớp dữ liệu trong Kotlin đem lại thêm một số lợi ích, 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 first_name: String, val last_name: 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 cần có ít nhất một tham số.
- Tất cả tham số của hàm khởi tạo chính đều phải được đánh dấu là
val
hoặcvar
. - Các lớp dữ liệu không được có trạng thái
abstract
,open
,sealed
hoặcinner
.
Để tìm hiểu thêm về lớp Dữ liệu, hãy xem tài liệu này.
- Chuyển đổi lớp
Item
thành lớp dữ liệu bằng cách thêm từ khoádata
vào tiền tố của khai báo lớp.
data class Item(
val id: Int = 0,
val itemName: String,
val itemPrice: Double,
val quantityInStock: 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
để cung cấpitem
làm tên bảng SQLite.
@Entity(tableName = "item")
data class Item(
...
)
- Để khai báo
id
làm khoá chính, hãy chú giải thuộc tínhid
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ặt hàng là giá trị duy nhất.
@Entity(tableName = "item")
data class Item(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
...
)
- Chú giải các thuộc tính còn lại bằng
@ColumnInfo
. Chú giảiColumnInfo
được dùng để tuỳ chỉnh cột liên kết với trường cụ thể. Ví dụ: khi sử dụng đối sốname
, bạn có thể chỉ định tên cột khác cho trường thay vì tên biến. Tuỳ chỉnh tên thuộc tính bằng cách sử dụng các tham số như dưới đây. Phương pháp này tương tự như sử dụngtableName
để chỉ định tên khác cho cơ sở dữ liệu.
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity
data class Item(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
@ColumnInfo(name = "name")
val itemName: String,
@ColumnInfo(name = "price")
val itemPrice: Double,
@ColumnInfo(name = "quantity")
val quantityInStock: Int
)
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)
Đối tượng truy cập dữ liệu (DAO) là một mẫu dùng để phân tách tầng cố định vớ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 tất cả 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 tầng truy cập dữ liệu có thể thay đổi độc lập với mã sử dụng dữ liệu đó.
Trong nhiệm vụ này, bạn sẽ khai báo một Đối tượng truy cập dữ liệu (DAO) cho Room. Đối tượng truy cập dữ liệu là các thành phần chính của Room, chịu trách nhiệm xác định giao diện truy cập vào cơ sở dữ liệu.
DAO mà bạn tạo sẽ là một giao diện tuỳ chỉnh cung cấp các phương thức thuận tiện để truy vấn/truy xuất, chèn, xoá và cập nhật cơ sở dữ liệu. Room sẽ tạo mã triển khai của lớp này tại thời điểm biên dịch.
Đố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 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 kiểm kho, bạn cần có khả năng làm 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.
Bây giờ, hãy triển khai DAO của mặt hàng trong ứng dụng:
- Trong gói
data
, hãy tạo lớp Kotlin làItemDao.kt
. - Hãy thay đổi khai báo lớp thành
interface
và chú giải bằng@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ố. Các thao tác với cơ sở dữ liệu có thể mất nhiều thời gian để thực thi nên sẽ chạy trên một luồng riêng. Thiết lập hàm này thành hàm tạm ngưng để hàm này có thể được gọi qua một coroutine.
@Insert
suspend fun insert(item: Item)
- 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ượcOnConflictStrategy.
IGNORE
sẽ bỏ qua một mặt hàng mới nếu khoá chính đã có trong cơ sở dữ liệu. Để tìm hiểu thêm về các chiến lược gây xung đột hiện có, hãy xem tài liệu này.
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(item: Item)
Bây giờ, 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 insert()
qua mã Kotlin, Room
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ể đặt tên hàm này theo ý muốn, không nhất thiết phải là insert()
.)
- Thêm chú giải
@Update
có hàmupdate()
cho mộtitem
. 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ứcinsert()
, hãy tạo phương thứcupdate()
saususpend
.
@Update
suspend fun update(item: Item)
- Thêm chú giải
@Delete
có hàmdelete()
để xoá (các) mặt hàng. Thiết lập chú giải này thành phương thức tạm ngưng. Chú giải@Delete
sẽ xoá một mặt hàng hoặc một danh sách nhiều mặt hàng. (Lưu ý: Bạn cần truyền thực thể cần xoá. Nếu không có thực thể, có thể bạn sẽ phải tìm nạp trước khi gọi hàmdelete()
.)
@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. Sau đó, bạn sẽ thêm chú giải Room và sử dụng phiên bản đã sửa đổi của truy vấn sau trong các bước tiếp theo. Trong các bước tiếp theo, bạn cũng sẽ thay đổi chú giải này thành phương thức DAO bằng cách sử dụng Room. - Chọn tất cả các cột qua
item
- Tìm
WHERE
cóid
khớp với một giá trị cụ thể.
Ví dụ:
SELECT * from item WHERE id = 1
- Thay đổi truy vấn SQL ở trên để sử dụng với chú thích Room và đối số. Thêm chú giải
@Query
, cung cấp truy vấn 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. - Chọn tất cả các cột qua
item
- Tìm
WHERE
cóid
khớp với đối số :id
. Hãy lưu ý:id
. 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.
@Query("SELECT * from item WHERE id = :id")
- Dưới chú giải
@Query
, hãy thêm hàmgetItem()
, hàm này nhận đối sốInt
và trả vềFlow<Item>
.
@Query("SELECT * from item WHERE id = :id")
fun getItem(id: Int): Flow<Item>
Việc sử dụng Flow
hoặc LiveData
làm loại dữ liệu trả về sẽ đảm bảo bạn nhận được thông báo mỗi khi dữ liệu trong cơ sở dữ liệu thay đổi. Bạn nên sử dụng Flow
trong tầng cố định. Room
giúp Flow
này luôn 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ách này hữu ích khi cập nhật danh sách 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.
Có thể bạn cần nhập Flow
từ kotlinx.coroutines.flow.Flow
.
- Thêm
@Query
có hàmgetItems()
: - 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
getItems()
trả về danh sách các thực thểItem
dưới dạngFlow
.Room
giúpFlow
này luôn 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 item ORDER BY name ASC")
fun getItems(): Flow<List<Item>>
- 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.
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 mà bạn đã tạo trong nhiệm vụ trước đó. Lớp cơ sở dữ liệu xác định danh sách các thực thể và các đối tượng truy cập dữ liệu. Đây cũng là điểm truy cập chính của kết nối cơ sở.
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 đã khai báo. Ứng dụng có thể sử dụng DAO để truy xuất dữ liệu qua 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ể sử dụng các thực thể dữ liệu đã khai báo để 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, có chú giải bằng @Database
. Lớp này có một phương thức sẽ tạo phiên bản thể hiện của RoomDatabase
nếu chưa có, hoặc trả về phiên bản thể hiện đang có của RoomDatabase
.
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 đã khai báo hoạt động như một lớp lưu giữ cơ sở dữ liệu. Lớp mà bạn đã khai báo là lớp trừu tượng vìRoom
tạo ra mã 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. - Xác định 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 mã 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 Database
- Trong gói
data
, hãy tạo một lớp Kotlin làItemRoomDatabase.kt
. - Trong tệp
ItemRoomDatabase.kt
, hãy thiết lập lớpItemRoomDatabase
làm lớpabstract
, lớp này mở rộngRoomDatabase
. Chú giải lớp này bằng@Database
. Bạn sẽ khắc phục lỗi thiếu tham số trong bước tiếp theo.
@Database
abstract class ItemRoomDatabase : 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
. Mỗi lần thay đổi giản đồ của bảng cơ sở dữ liệu, 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 đồ.
@Database(entities = [Item::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 hàm trừu tượng trả về
ItemDao
. Bạn có thể có nhiều DAO.
abstract fun itemDao(): ItemDao
- Bên dưới hàm trừu tượng này, hãy xác định một đối tượng
companion
. Đối tượng đồng hành này 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 bằng cách 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ế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 đả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 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.
@Volatile
private var INSTANCE: ItemRoomDatabase? = 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 loạiItemRoomDatabase
. Bạn sẽ thấy lỗi vìgetDatabase()
chưa trả về giá trị nào.
fun getDatabase(context: Context): ItemRoomDatabase {}
- Nhiều luồng có nguy cơ gặp phải tình huống tương tranh (race condition) và 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. 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.
Bên trong getDatabase()
, hãy trả về biến INSTANCE
hoặc nếu INSTANCE
rỗng, hãy khởi tạo biến đó bên trong khối synchronized{}
. Hãy dùng toán tử elvis (?:
) để thực hiện thao tác này. Truyền vào this
đối tượng đồng hành mà bạn muốn cố định trong khối của hàm. 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 tạo biến phiên bản
val
và sử dụng trình tạo cơ sở dữ liệu để tải cơ sở dữ liệu. Bạn sẽ vẫn gặp lỗi nhưng sẽ khắc phục trong các bước tiếp theo.
val instance = Room.databaseBuilder()
- Ở cuối khối
synchronized
, hãy trả vềinstance
.
return instance
- Bên trong khối
synchronized
, hãy khởi tạo biếninstance
và sử dụng hàm 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()
.
val instance = Room.databaseBuilder(
context.applicationContext,
ItemRoomDatabase::class.java,
"item_database"
)
Android Studio sẽ cho ra lỗi Loại không khớp (Type Mismatch). Để 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.
- 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 (migration object) 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. Chiến lược 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()
- Để tạo phiên bản thể hiện của cơ sở dữ liệu, hãy gọi
.build()
. Thao tác này sẽ xoá các lỗi trên Android Studio.
.build()
- Chỉ định
INSTANCE = instance
trong bên trong khốisynchronized
.
INSTANCE = instance
- Ở cuối khối
synchronized
, hãy trả vềinstance
. Mã hoàn thiện 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(entities = [Item::class], version = 1, exportSchema = false)
abstract class ItemRoomDatabase : RoomDatabase() {
abstract fun itemDao(): ItemDao
companion object {
@Volatile
private var INSTANCE: ItemRoomDatabase? = null
fun getDatabase(context: Context): ItemRoomDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
ItemRoomDatabase::class.java,
"item_database"
)
.fallbackToDestructiveMigration()
.build()
INSTANCE = instance
return instance
}
}
}
}
- Xây dựng mã của bạn để đảm bảo không có lỗi.
Triển khai lớp Application
Trong nhiệm vụ này, bạn sẽ tạo phiên bản thể hiện của cơ sở dữ liệu trong lớp Ứng dụng.
- Mở
InventoryApplication.kt
rồi tạoval
tên làdatabase
thuộc loạiItemRoomDatabase
. Tạo phiên bản thể hiệndatabase
bằng cách gọigetDatabase()
trênItemRoomDatabase
, giá trị truyền vào là ngữ cảnh. Sử dụng đối tượng uỷ quyềnlazy
để trì hoãn việc tạo phiên bản thể hiệndatabase
đến khi bạn cần/truy cập tham chiếu lần đầu tiên (thay vì tạo khi ứng dụng khởi động). Cách này sẽ tạo cơ sở dữ liệu (cơ sở dữ liệu thực trên đĩa) trong lần truy cập đầu tiên.
import android.app.Application
import com.example.inventory.data.ItemRoomDatabase
class InventoryApplication : Application(){
val database: ItemRoomDatabase by lazy { ItemRoomDatabase.getDatabase(this) }
}
Trong các bước sau, bạn sẽ sử dụng phiên bản database
này trong lớp học lập trình khi tạo một phiên bản thể hiện của ViewModel.
Hiện bạn đã có tất cả yếu tố nền móng để xử lý 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 hàng mới vào cơ sở dữ liệu Kiểm kho của bạn để kiểm tra xem cơ sở dữ liệu có hoạt đông hay không. Để thực hiện việc này, bạn cần có ViewModel
để giao tiếp với cơ sở dữ liệu.
8. Thêm ViewModel
Đế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ó ViewModel. ViewModel của ứng dụng Kiểm kho sẽ 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 thao tác về cơ sở dữ liệu sẽ phải chạy trên 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
.
Tạo ViewModel cho dữ liệu kiểm kho
- Trong gói
com.example.inventory
, hãy tạo một tệp lớp Kotlin làInventoryViewModel.kt
. - Mở rộng lớp
InventoryViewModel
từ lớpViewModel
. Truyền đối tượngItemDao
dưới dạng một tham số vào hàm khởi tạo mặc định.
class InventoryViewModel(private val itemDao: ItemDao) : ViewModel() {}
- Ở cuối tệp
InventoryViewModel.kt
bên ngoài lớp, hãy thêm lớpInventoryViewModelFactory
để tạo phiên bản thể hiệnInventoryViewModel
. Truyền vào cùng tham số hàm khởi tạo giống như đối vớiInventoryViewModel
, tức là phiên bản thể hiệnItemDao
. Mở rộng lớp này từ lớpViewModelProvider.Factory
. Bạn sẽ khắc phục lỗi liên quan đến các phương thức chưa triển khai trong bước tiếp theo.
class InventoryViewModelFactory(private val itemDao: ItemDao) : ViewModelProvider.Factory {
}
- Nhấp vào bóng đèn màu đỏ rồi chọn Implement Members (Triển khai thành phần), hoặc bạn có thể ghi đè phương thức
create()
bên trong lớpViewModelProvider.Factory
như sau (thao tác này sẽ lấy một loại lớp bất kỳ làm đối số và trả vềViewModel
).
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
TODO("Not yet implemented")
}
- Triển khai phương thức
create()
. Hãy kiểm tra xemmodelClass
có giống với lớpInventoryViewModel
hay không và trả về một phiên bản thể hiện của lớp này. Nếu không, hãy gửi một ngoại lệ.
if (modelClass.isAssignableFrom(InventoryViewModel::class.java)) {
@Suppress("UNCHECKED_CAST")
return InventoryViewModel(itemDao) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
Điền vào ViewModel
Trong nhiệm vụ này, bạn sẽ điền vào lớp InventoryViewModel
để thêm dữ liệu kiểm kho vào cơ sở dữ liệu. Quan sát thực thể Item
và màn hình Add Item (Thêm mặt hàng) trong ứng dụng Inventory.
@Entity
data class Item(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
@ColumnInfo(name = "name")
val itemName: String,
@ColumnInfo(name = "price")
val itemPrice: Double,
@ColumnInfo(name = "quantity")
val quantityInStock: Int
)
Bạn cần có tên, giá và số lượng trong kho cho mặt hàng cụ thể đó để có thể thêm một thực thể vào cơ sở dữ liệu. Trong phần sau của lớp học lập trình này, bạn sẽ dùng màn hình Add Item (Thêm mặt hàng) để lấy những thông tin này từ người dùng. Trong nhiệm vụ hiện tại, bạn sử dụng 3 chuỗi làm dữ liệu đầu vào cho ViewModel, chuyển đổi các chuỗi này thành một phiên bản thể hiện của thực thể Item
và lưu vào cơ sở dữ liệu bằng phiên bản thể hiện của ItemDao
. Giờ đã đến lúc triển khai.
- Trong lớp
InventoryViewModel
, hãy thêm hàmprivate
có tên làinsertItem()
. Hàm này chứa đối tượngItem
và thêm dữ liệu vào cơ sở dữ liệu theo cách không chặn luồng thực thi.
private fun insertItem(item: Item) {
}
- Để tương tác với cơ sở dữ liệu ngoài luồng chính, hãy bắt đầu một coroutine rồi gọi phương thức DAO trong coroutine đó. Bên trong phương thức
insertItem()
, hãy sử dụngviewModelScope.launch
để bắt đầu một coroutine trongViewModelScope
. Bên trong hàm khởi chạy, hãy gọi hàm tạm ngưnginsert()
trênitemDao
, giá trị truyền vào làitem
.ViewModelScope
là một thuộc tính mở rộng cho lớpViewModel
. Lớp này tự động huỷ các coroutine con của khiViewModel
bị huỷ bỏ.
private fun insertItem(item: Item) {
viewModelScope.launch {
itemDao.insert(item)
}
}
Nhập kotlinx.coroutines.launch,
androidx.lifecycle.
viewModelScope
com.example.inventory.data.Item
, nếu không được nhập tự động.
- Trong lớp
InventoryViewModel
, hãy thêm một hàm riêng tư khác chứa 3 chuỗi và trả về phiên bản thể hiệnItem
.
private fun getNewItemEntry(itemName: String, itemPrice: String, itemCount: String): Item {
return Item(
itemName = itemName,
itemPrice = itemPrice.toDouble(),
quantityInStock = itemCount.toInt()
)
}
- Vẫn bên trong lớp
InventoryViewModel
, hãy thêm một hàm công khai có tên làaddNewItem()
. Hàm này chứa 3 chuỗi để hiện thông tin chi tiết về mặt hàng. Truyền chuỗi thông tin về mặt hàng vào hàmgetNewItemEntry()
và gán giá trị được trả về cho một val có tên lànewItem
. GọiinsertItem()
với giá trị truyền vào lànewItem
để thêm thực thể mới vào cơ sở dữ liệu. Lệnh gọi được thực hiện từ mảnh giao diện người dùng để thêm thông tin chi tiết về Item (mặt hàng) vào cơ sở dữ liệu.
fun addNewItem(itemName: String, itemPrice: String, itemCount: String) {
val newItem = getNewItemEntry(itemName, itemPrice, itemCount)
insertItem(newItem)
}
Xin lưu ý rằng bạn không sử dụng viewModelScope.launch
cho addNewItem()
, nhưng vẫn phải thêm vào bên trên insertItem()
khi bạn gọi phương thức DAO. Lý do là lệnh gọi hàm tạm ngưng chỉ được phép đến từ một coroutine hoặc một hàm tạm ngưng khác. Hàm itemDao.insert(item)
là hàm tạm ngưng.
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 mảnh Add Item (Thêm mặt hàng) để sử dụng các hàm trên.
9. Cập nhật AddItemFragment
- Trong
AddItemFragment.kt
, ở đầu lớpAddItemFragment
, hãy tạo mộtprivate val
tên làviewModel
thuộc loạiInventoryViewModel
. Sử dụng đối tượng uỷ quyền thuộc tínhby activityViewModels()
Kotlin để chia sẻViewModel
giữa các mảnh. Bạn sẽ khắc phục lỗi trong bước tiếp theo.
private val viewModel: InventoryViewModel by activityViewModels {
}
- Bên trong hàm lambda, hãy gọi hàm khởi tạo
InventoryViewModelFactory()
và truyền vào phiên bản thể hiệnItemDao
. Sử dụng phiên bản thể hiệndatabase
mà bạn đã tạo ở một nhiệm vụ trước đó để gọi hàm khởi tạoitemDao
.
private val viewModel: InventoryViewModel by activityViewModels {
InventoryViewModelFactory(
(activity?.application as InventoryApplication).database
.itemDao()
)
}
- Bên dưới phần khai báo
viewModel
, hãy tạolateinit var
có tên làitem
thuộc loạiItem
.
lateinit var item: Item
- Màn hình Add Item (Thêm mặt hàng) chứa 3 trường văn bản để lấy thông tin chi tiết về mặt hàng từ người dùng. Ở bước này, bạn sẽ thêm một hàm để xác minh xem văn bản trong TextField (trường văn bản) có trống không. Bạn sẽ dùng hàm này để xác minh dữ liệu đầ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. Bạn cần thực hiện quy trình xác thực này trong
ViewModel
, chứ không phải trong mảnh. Trong lớpInventoryViewModel
, hãy thêm hàmpublic
có tên làisEntryValid()
như sau.
fun isEntryValid(itemName: String, itemPrice: String, itemCount: String): Boolean {
if (itemName.isBlank() || itemPrice.isBlank() || itemCount.isBlank()) {
return false
}
return true
}
- Trong
AddItemFragment.kt
, dưới hàmonCreateView()
, hãy tạo một hàmprivate
có tên làisEntryValid()
, hàm này trả về mộtBoolean
. Bạn sẽ khắc phục lỗi thiếu giá trị trả về trong bước tiếp theo.
private fun isEntryValid(): Boolean {
}
- Trong lớp
AddItemFragment
, hãy triển khai hàmisEntryValid()
. Gọi hàmisEntryValid()
trên phiên bản thể hiệnviewModel
với giá trị truyền vào là văn bản từ thành phần hiển thị văn bản. Trả về giá trị của hàmviewModel.isEntryValid()
.
private fun isEntryValid(): Boolean {
return viewModel.isEntryValid(
binding.itemName.text.toString(),
binding.itemPrice.text.toString(),
binding.itemCount.text.toString()
)
}
- Trong lớp
AddItemFragment
bên dưới hàmisEntryValid()
, hãy thêm một hàmprivate
khác có tên làaddNewItem()
, hàm này không có tham số và không trả về giá trị nào. Bên trong hàm này, hãy gọiisEntryValid()
bên trong điều kiệnif
.
private fun addNewItem() {
if (isEntryValid()) {
}
}
- Trong khối
if
, hãy gọi phương thứcaddNewItem()
trên phiên bản thể hiệnviewModel
. Truyền thông tin chi tiết về mặt hàng mà người dùng đã nhập, sử dụng phiên bản thể hiệnbinding
để đọc thông tin đó.
if (isEntryValid()) {
viewModel.addNewItem(
binding.itemName.text.toString(),
binding.itemPrice.text.toString(),
binding.itemCount.text.toString(),
)
}
- Bên dưới khối
if
, hãy tạoaction
val
để quay lạiItemListFragment
. Hãy gọifindNavController
().navigate()
với giá trị trả về làaction
.
val action = AddItemFragmentDirections.actionAddItemFragmentToItemListFragment()
findNavController().navigate(action)
Nhập androidx.navigation.fragment.findNavController.
- Phương thức hoàn chỉnh sẽ có dạng như sau.
private fun addNewItem() {
if (isEntryValid()) {
viewModel.addNewItem(
binding.itemName.text.toString(),
binding.itemPrice.text.toString(),
binding.itemCount.text.toString(),
)
val action = AddItemFragmentDirections.actionAddItemFragmentToItemListFragment()
findNavController().navigate(action)
}
}
- Để 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). Trong lớp
AddItemFragment
, phía trên hàmonDestroyView()
, hãy ghi đè hàmonViewCreated()
. - Bên trong hàm
onViewCreated()
, hãy thêm một trình xử lý lượt nhấp vào nút lưu và gọiaddNewItem()
từ trình xử lý đó.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.saveAction.setOnClickListener {
addNewItem()
}
}
- Tạo bản dựng và chạy ứng dụng của bạn. Nhấn vào + Fab. 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). Thao tác này sẽ lưu dữ liệu, nhưng bạn chưa thể xem nội dung nào 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 bạn đã lưu.
Xem cơ sở dữ liệu bằng Trình kiểm tra cơ sở dữ liệu
- 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). 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.
- Trong Android Studio, trên thanh trình đơn, hãy chọn View (Thành phần hiển thị) > Tool Windows (Cửa sổ công cụ) > 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. - item_database trong ứng dụng Kiểm kho xuất hiện trong ngăn Databases (Cơ sở dữ liệu). Mở rộng nút (node) cho item_database 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 cách sử dụng màn hình Add Item (Thêm mặt hàng).
- Chọn hộp đánh dấu Live updates (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 đã tạo một ứng dụng có thể giữ nguyên dữ liệu bằng cách sử dụng Room. Trong lớp học lập trình tiếp theo, bạn sẽ thêm RecyclerView
vào ứng dụng của mình để hiện 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ư tính năng xoá và cập nhật các thực thể. Hẹn gặp bạn ở lớp học sau nhé!
10. Mã giải pháp
Mã giải pháp cho lớp học lập trình này nằm trong kho lưu trữ và nhánh GitHub dưới đây.
Để lấy mã nguồn cho lớp học lập trình này và mở trong Android Studio, hãy làm như sau.
Lấy mã
- Nhấp vào URL được cung cấp. Thao tác này sẽ mở trang GitHub của dự án trong một trình duyệt.
- Trên trang GitHub của dự án, hãy nhấp vào nút Code (Mã), một hộp thoại sẽ xuất hiện.
- Trong hộp thoại này, hãy nhấp vào nút Download ZIP (Tải tệp ZIP xuống) để lưu dự án vào máy tính. Chờ quá trình tải xuống hoàn tất.
- Xác định vị trí của tệp trên máy tính (có thể trong thư mục Downloads (Tệp đã tải xuống)).
- Nhấp đúp vào tệp ZIP để giải nén. Thao tác này sẽ tạo một thư mục mới chứa các tệp dự án.
Mở dự án trong Android Studio
- Khởi động Android Studio.
- Trong cửa sổ Welcome to Android Studio (Chào mừng bạn đến với Android Studio), hãy nhấp vào Open an existing Android Studio project (Mở một dự án hiện có trong Android Studio).
Lưu ý: Nếu Android Studio đã mở sẵn thì thay vào đó, hãy chọn tuỳ chọn sau đây trong trình đơn File > New > Import Project (Tệp > Mới > Nhập dự án).
- Trong hộp thoại Import Project (Nhập dự án), hãy chuyển đến nơi chứa thư mục dự án đã giải nén (có thể trong thư mục Downloads (Tệp đã tải xuống)).
- Nhấp đúp vào thư mục dự án đó.
- Chờ Android Studio mở dự án.
- Nhấp vào nút Run (Chạy) để tạo và chạy ứng dụng. Đảm bảo ứng dụng được xây dựng như mong đợi.
- Duyệt qua các tệp dự án trong cửa sổ công cụ Project (Dự án) để xem cách ứng dụng được thiết lập.
11. 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.
12. 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