Duy trì dữ liệu thông qua Room

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.

7521165e051cc0d4.png

Đ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, LiveDataFlow, cũng như biết cách sử dụng ViewModelProvider.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.

439ad9a8183278c5.png

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ã

  1. 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.
  2. 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.

5b0a76c50478a73f.png

  1. 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.
  2. 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)).
  3. 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

  1. Khởi động Android Studio.
  2. 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).

36cc44fcf0f89a1d.png

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).

21f3eec988dcfbe9.png

  1. 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)).
  2. Nhấp đúp vào thư mục dự án đó.
  3. Chờ Android Studio mở dự án.
  4. Nhấp vào nút Run (Chạy) 11c34fc5e516fb1c.png để tạo và chạy ứng dụng. Đảm bảo ứng dụng được xây dựng như mong đợi.
  5. 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

  1. Mở dự án bằng mã khởi động trong Android Studio.
  2. 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.
  3. Ứ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.
  4. 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.

9c5e361a89453821.png

Vấn đề liên quan đến mã khởi động

  1. 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.

f0931dab5089a14f.png

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.

33a193a68c9a8e0e.png

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.

  1. Mở tệp gradle cấp mô-đun build.gradle (Module: InventoryApp.app). Trong khối dependencies, 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.

8c9f1659ee82ca43.png

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.
  1. Mở mã khởi động trong Android Studio.
  2. Tạo gói có tên là data trong gói cơ sở com.example.inventory.

be39b42484ba2664.png

  1. 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.
  2. Hãy cập nhật khai báo lớp Item bằng mã như sau. Khai báo id thuộc loại Int, itemName thuộc loại String, itemPrice thuộc loại DoublequantityInStock thuộc loại Int làm tham số cho hàm khởi tạo chính. Gán giá trị mặc định từ 0 đến id. Đâ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ảng Item.
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()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ặc var.
  • Các lớp dữ liệu không được có trạng thái abstract, open, sealed hoặc inner.

Để tìm hiểu thêm về lớp Dữ liệu, hãy xem tài liệu này.

  1. 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
)
  1. 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ấp item làm tên bảng SQLite.
@Entity(tableName = "item")
data class Item(
   ...
)
  1. Để khai báo id làm khoá chính, hãy chú giải thuộc tính id bằng @PrimaryKey. Hãy thiết lập tham số autoGenerate thành true để 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,
   ...
)
  1. Chú giải các thuộc tính còn lại bằng @ColumnInfo. Chú giải ColumnInfo đượ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ụng tableName để 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 đó.

7a8480711f04b3ef.png

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@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.

bb381857d5fba511.png

Bây giờ, hãy triển khai DAO của mặt hàng trong ứng dụng:

  1. Trong gói data, hãy tạo lớp Kotlin là ItemDao.kt.
  2. Hãy thay đổi khai báo lớp thành interface và chú giải bằng @Dao.
@Dao
interface ItemDao {
}
  1. 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àm insert(), hàm này lấy phiên bản thể hiện của lớp Entity của item 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)
  1. 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ặ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().)

  1. Thêm chú giải @Update có hàm update() cho một item. 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 tạo phương thức update() sau suspend.
@Update
suspend fun update(item: Item)
  1. Thêm chú giải @Delete có hàm delete() để 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àm 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.

  1. 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.
  2. Chọn tất cả các cột qua item
  3. Tìm WHEREid khớp với một giá trị cụ thể.

Ví dụ:

SELECT * from item WHERE id = 1
  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.
  2. Chọn tất cả các cột qua item
  3. Tìm WHEREid 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")
  1. Dưới chú giải @Query, hãy thêm hàm getItem(), 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.

  1. Thêm @Query có hàm getItems():
  2. 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.
  3. Yêu cầu getItems() trả về danh sách các thực thể Item dưới dạng Flow. 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.
@Query("SELECT * from item ORDER BY name ASC")
fun getItems(): Flow<List<Item>>
  1. 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ộng RoomDatabase. 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 ItemDaoRoom 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ập RoomDatabase thành một singleton.
  • Chỉ sử dụng Room.databaseBuilder của Room để 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

  1. Trong gói data, hãy tạo một lớp Kotlin là ItemRoomDatabase.kt.
  2. Trong tệp ItemRoomDatabase.kt, hãy thiết lập lớp ItemRoomDatabase làm lớp abstract, lớp này mở rộng RoomDatabase. 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() {}
  1. 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ách entities.
  • Thiết lập version thành 1. 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ành false để 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)
  1. 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
  1. 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 {}
  1. Bên trong đối tượng companion, hãy khai báo INSTANCE (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 đó đến null. Biến INSTANCE 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
  1. Bên dưới INSTANCE, vẫn bên trong đối tượng companion, hãy khai báo một phương thức getDatabase() có tham số Context mà hàm tạo cơ sở dữ liệu sẽ cần đến. Trả về một loại ItemRoomDatabase. Bạn sẽ thấy lỗi vì getDatabase() chưa trả về giá trị nào.
fun getDatabase(context: Context): ItemRoomDatabase {}
  1. 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) { }
  1. 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()
  1. Ở cuối khối synchronized, hãy trả về instance.
return instance
  1. Bên trong khối synchronized, hãy khởi tạo biến instance 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ào Room.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.

  1. 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()
  1. Để 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()
  1. Chỉ định INSTANCE = instance trong bên trong khối synchronized.
INSTANCE = instance
  1. Ở 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
           }
       }
   }
}
  1. 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.

  1. Mở InventoryApplication.kt rồi tạo val tên là database thuộc loại ItemRoomDatabase. Tạo phiên bản thể hiện database bằng cách gọi getDatabase() trên ItemRoomDatabase, giá trị truyền vào là ngữ cảnh. Sử dụng đối tượng uỷ quyền lazy để trì hoãn việc tạo phiên bản thể hiện database đế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.

91298a7c05e4f5e0.png

Tạo ViewModel cho dữ liệu kiểm kho

  1. Trong gói com.example.inventory, hãy tạo một tệp lớp Kotlin là InventoryViewModel.kt.
  2. Mở rộng lớp InventoryViewModel từ lớp ViewModel. Truyền đối tượng ItemDao 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() {}
  1. Ở cuối tệp InventoryViewModel.kt bên ngoài lớp, hãy thêm lớp InventoryViewModelFactory để tạo phiên bản thể hiện InventoryViewModel. Truyền vào cùng tham số hàm khởi tạo giống như đối với InventoryViewModel, tức là phiên bản thể hiện ItemDao. Mở rộng lớp này từ lớp ViewModelProvider.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 {
}
  1. 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ớp ViewModelProvider.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")
}
  1. Triển khai phương thức create(). Hãy kiểm tra xem modelClass có giống với lớp InventoryViewModel 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
)

85c644aced4198c5.png

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.

  1. Trong lớp InventoryViewModel, hãy thêm hàm private có tên là insertItem(). Hàm này chứa đối tượng Item 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) {
}
  1. Để 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ụng viewModelScope.launch để bắt đầu một coroutine trong ViewModelScope. Bên trong hàm khởi chạy, hãy gọi hàm tạm ngưng insert() trên itemDao, giá trị truyền vào là item. ViewModelScope là một thuộc tính mở rộng cho lớp ViewModel. Lớp này tự động huỷ các coroutine con của khi ViewModel 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.

  1. 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ện Item.
private fun getNewItemEntry(itemName: String, itemPrice: String, itemCount: String): Item {
   return Item(
       itemName = itemName,
       itemPrice = itemPrice.toDouble(),
       quantityInStock = itemCount.toInt()
   )
}
  1. 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àm getNewItemEntry() và gán giá trị được trả về cho một val có tên là newItem. Gọi insertItem() 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

  1. Trong AddItemFragment.kt, ở đầu lớp AddItemFragment, hãy tạo một private val tên là viewModel thuộc loại InventoryViewModel. Sử dụng đối tượng uỷ quyền thuộc tính by 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 {
}
  1. 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ện ItemDao. Sử dụng phiên bản thể hiện database mà bạn đã tạo ở một nhiệm vụ trước đó để gọi hàm khởi tạo itemDao.
private val viewModel: InventoryViewModel by activityViewModels {
   InventoryViewModelFactory(
       (activity?.application as InventoryApplication).database
           .itemDao()
   )
}
  1. Bên dưới phần khai báo viewModel, hãy tạo lateinit var có tên là item thuộc loại Item.
 lateinit var item: Item
  1. 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ớp InventoryViewModel, hãy thêm hàm public 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
}
  1. Trong AddItemFragment.kt, dưới hàm onCreateView(), hãy tạo một hàm private có tên là isEntryValid(), hàm này trả về một Boolean. 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 {
}
  1. Trong lớp AddItemFragment, hãy triển khai hàm isEntryValid(). Gọi hàm isEntryValid() trên phiên bản thể hiện viewModel 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àm viewModel.isEntryValid().
private fun isEntryValid(): Boolean {
   return viewModel.isEntryValid(
       binding.itemName.text.toString(),
       binding.itemPrice.text.toString(),
       binding.itemCount.text.toString()
   )
}
  1. Trong lớp AddItemFragment bên dưới hàm isEntryValid(), hãy thêm một hàm private 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ọi isEntryValid() bên trong điều kiện if.
private fun addNewItem() {
   if (isEntryValid()) {
   }
}
  1. Trong khối if, hãy gọi phương thức addNewItem() trên phiên bản thể hiện viewModel. 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ện binding để đọc thông tin đó.
if (isEntryValid()) {
   viewModel.addNewItem(
   binding.itemName.text.toString(),
   binding.itemPrice.text.toString(),
   binding.itemCount.text.toString(),
   )
}
  1. Bên dưới khối if, hãy tạo action val để quay lại ItemListFragment. Hãy gọi findNavController().navigate() với giá trị trả về là action.
val action = AddItemFragmentDirections.actionAddItemFragmentToItemListFragment()
findNavController().navigate(action)

Nhập androidx.navigation.fragment.findNavController.

  1. 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)
       }
}
  1. Để 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àm onDestroyView(), hãy ghi đè hàm onViewCreated().
  2. 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ọi addNewItem() từ trình xử lý đó.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
   super.onViewCreated(view, savedInstanceState)
   binding.saveAction.setOnClickListener {
       addNewItem()
   }
}
  1. 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.

193c7fa9c41e0819.png

Xem cơ sở dữ liệu bằng Trình kiểm tra cơ sở dữ liệu

  1. 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.
  2. 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).
  3. 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.
  4. 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).
  5. 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ị.

4803c08f94e34118.png

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ã

  1. 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.
  2. 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.

5b0a76c50478a73f.png

  1. 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.
  2. 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)).
  3. 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

  1. Khởi động Android Studio.
  2. 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).

36cc44fcf0f89a1d.png

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).

21f3eec988dcfbe9.png

  1. 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)).
  2. Nhấp đúp vào thư mục dự án đó.
  3. Chờ Android Studio mở dự án.
  4. Nhấp vào nút Run (Chạy) 11c34fc5e516fb1c.png để tạo và chạy ứng dụng. Đảm bảo ứng dụng được xây dựng như mong đợi.
  5. 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@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

Bài đăng trên blog

Video

Các tài liệu và bài viết khác