Android Kotlin 基礎知識 06.1:建立 Room 資料庫

這個程式碼研究室是 Android Kotlin 基礎知識課程的一部分。如果您按部就班完成程式碼研究室,就能充分體驗到本課程的價值。所有課程程式碼研究室都列在 Android Kotlin 基礎知識程式碼研究室到達網頁

簡介

大多數應用程式都有需要保留的資料,即使使用者關閉應用程式也不例外。舉例來說,應用程式可能會儲存播放清單、遊戲物品清單、支出和收入記錄、星座目錄或一段時間內的睡眠資料。一般來說,您會使用資料庫儲存持續性資料。

Room 是 Android Jetpack 中的資料庫程式庫。Room 可處理許多設定和設定資料庫的雜務,讓您的應用程式能使用一般的函式呼叫與資料庫互動。在幕後,Room 是 SQLite 資料庫頂端的抽象層。Room 的術語,以及更複雜查詢的查詢語法,都遵循 SQLite 模型。

下圖說明瞭 Room 資料庫如何配合本課程推薦的整體架構。

必備知識

您必須已經熟悉下列項目:

  • 為 Android 應用程式建構基本的使用者介面 (UI)
  • 使用活動、片段和檢視畫面。
  • 在片段之間導覽,並使用 Safe Args (Gradle 外掛程式) 在片段之間傳遞資料。
  • 查看模型、ViewModel 工廠,以及 LiveData 和其觀察器。本課程先前的程式碼研究室已涵蓋這些架構元件主題。
  • 對 SQL 資料庫和 SQLite 語言有基本瞭解。如要快速瞭解或複習相關概念,請參閱 SQLite 基礎知識

課程內容

  • 如何建立及操作 Room 資料庫,以便保存資料。
  • 如何建立定義資料庫中資料表的資料類別。
  • 如何使用資料存取物件 (DAO) 將 Kotlin 函式對應至 SQL 查詢。
  • 如何測試資料庫是否正常運作。

學習內容

  • 建立 Room 資料庫,並提供夜間睡眠資料的介面。
  • 使用提供的測試項目測試資料庫。

在本程式碼研究室中,您將建構應用程式的資料庫部分,該部分會追蹤睡眠品質。應用程式會使用資料庫儲存一段時間內的睡眠資料。

如圖所示,這個應用程式有兩個畫面,分別以片段表示。

如左側所示,第一個畫面會顯示開始和停止追蹤的按鈕。畫面上會顯示使用者的所有睡眠資料。「清除」按鈕會永久刪除應用程式為使用者收集的所有資料。

右側的第二個畫面用於選取睡眠品質評分。應用程式會以數字表示評分。為了方便開發,應用程式會同時顯示臉部圖示和對應的數字。

使用者流程如下:

  • 使用者開啟應用程式,並看到睡眠追蹤畫面。
  • 使用者輕觸「開始」按鈕。系統會記錄開始時間並顯示。「開始」按鈕已停用,「停止」按鈕已啟用。
  • 使用者輕觸「停止」按鈕。系統會記錄結束時間,並開啟睡眠品質畫面。
  • 使用者選取睡眠品質圖示。螢幕會關閉,追蹤畫面則會顯示睡眠結束時間和睡眠品質。「停止」按鈕已停用,「開始」按鈕已啟用。應用程式已準備好迎接下一個夜晚。
  • 只要資料庫中有資料,「清除」按鈕就會啟用。使用者輕觸「清除」按鈕後,所有資料都會遭到清除,且無法復原,系統也不會顯示「確定要清除嗎?」訊息。

這個應用程式採用簡化架構,如下圖所示 (完整架構的背景資訊)。應用程式只會使用下列元件:

  • UI 控制器
  • 查看模型和 LiveData
  • Room 資料庫

步驟 1:下載並執行入門應用程式

  1. 從 GitHub 下載 TrackMySleepQuality-Starter 應用程式。
  2. 建構並執行應用程式。應用程式會顯示 SleepTrackerFragment 片段的 UI,但不會顯示任何資料。按鈕不會對輕觸動作做出反應。

步驟 2:檢查入門應用程式

  1. 查看 Gradle 檔案:
  • 專案 Gradle 檔案
    在專案層級的 build.gradle 檔案中,請注意指定程式庫版本的變數。範例應用程式中使用的版本可順暢搭配運作,且與這個應用程式相容。完成本程式碼研究室時,Android Studio 可能會提示您更新部分版本。您可以選擇更新或繼續使用應用程式中的版本。如果遇到「奇怪」的編譯錯誤,請嘗試使用最終解決方案應用程式所用的程式庫版本組合。
  • 模組 Gradle 檔案。請注意,所有 Android Jetpack 程式庫 (包括 Room) 和協同程式的依附元件都已提供。
  1. 請查看套件和 UI。應用程式是依功能架構而成。這個套件含有預留位置檔案,您會在這一系列的程式碼研究室中新增程式碼。
  • database 套件,適用於所有與 Room 資料庫相關的程式碼。
  • sleepqualitysleeptracker 套件包含每個畫面的片段、檢視模型和檢視模型工廠。
  1. 請查看 Util.kt 檔案,其中包含有助於顯示睡眠品質資料的函式。部分程式碼已加上註解,因為這些程式碼會參照您稍後建立的檢視區塊模型。
  2. 查看 androidTest 資料夾 (SleepDatabaseTest.kt)。您將使用這項測試驗證資料庫是否正常運作。

在 Android 中,資料會以資料類別表示,並透過函式呼叫存取及修改。但在資料庫中,您需要實體查詢

  • 實體代表要儲存在資料庫中的物件/概念及其屬性。實體類別定義了資料表,這個類別的每個例項都代表資料表中的一列;每個屬性都會定義一個資料欄。在應用程式中,這個實體會存放有關一晚睡眠的資訊,
  • 查詢是指從資料庫表格或表格組合要求資料或資訊,或是要求對資料執行動作。常見查詢是為了取得、插入及更新實體。舉例來說,你可以查詢所有記錄的睡眠時間,並依開始時間排序。

Room 會為您完成所有繁瑣的工作,將 Kotlin 資料類別轉換為可儲存在 SQLite 資料表中的實體,並將函式宣告轉換為 SQL 查詢。

您必須將每個實體定義為加上註解的資料類別,並將互動定義為加上註解的介面,也就是資料存取物件 (DAO)Room 會使用這些註解類別,在資料庫中建立資料表,以及對資料庫執行的查詢。

步驟 1:建立 SleepNight 實體

在這項工作中,您要將一晚的睡眠定義為註解資料類別。

如要記錄一晚的睡眠,請輸入開始時間、結束時間和睡眠品質評分。

此外,您還需要專屬 ID 來識別當晚。

  1. database 套件中,找出並開啟 SleepNight.kt 檔案。
  2. 建立 SleepNight 資料類別,並加入 ID、開始時間 (毫秒)、結束時間 (毫秒) 和睡眠品質評分 (數值) 的參數。
  • 您必須初始化 sleepQuality,因此請將其設為 -1,表示系統尚未收集任何品質資料。
  • 您也必須初始化結束時間。將其設為開始時間,表示尚未記錄結束時間。
data class SleepNight(
       var nightId: Long = 0L,
       val startTimeMilli: Long = System.currentTimeMillis(),
       var endTimeMilli: Long = startTimeMilli,
       var sleepQuality: Int = -1
)
  1. 在類別宣告前,為資料類別加上 @Entity 註解。將資料表命名為 daily_sleep_quality_table。雖然 tableName 的引數是選用引數,但我們建議使用。您可以在說明文件中查詢其他引數。

    如果系統提示,請從 androidx 程式庫匯入 Entity 和所有其他註解。
@Entity(tableName = "daily_sleep_quality_table")
data class SleepNight(...)
  1. 要將 nightId 標識為主鍵,請為 nightId 屬性加上註解 @PrimaryKey。將 autoGenerate 參數設為 true,以便 Room 為每個實體產生 ID。這能保證每晚的 ID 都不重複。
@PrimaryKey(autoGenerate = true)
var nightId: Long = 0L,...
  1. 為其餘屬性加上註解 @ColumnInfo。使用參數自訂屬性名稱,如下所示。
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity(tableName = "daily_sleep_quality_table")
data class SleepNight(
       @PrimaryKey(autoGenerate = true)
       var nightId: Long = 0L,

       @ColumnInfo(name = "start_time_milli")
       val startTimeMilli: Long = System.currentTimeMillis(),

       @ColumnInfo(name = "end_time_milli")
       var endTimeMilli: Long = startTimeMilli,

       @ColumnInfo(name = "quality_rating")
       var sleepQuality: Int = -1
)
  1. 建構並執行程式碼,確保沒有錯誤。

在這項工作中,您要定義資料存取物件 (DAO)。在 Android 上,DAO 提供插入、刪除及更新資料庫的簡便方法。

使用 Room 資料庫時,您可以在程式碼中定義及呼叫 Kotlin 函式,藉此查詢資料庫。這些 Kotlin 函式會對應至 SQL 查詢。您可以使用註解在 DAO 中定義這些對應項,而 Room 會建立必要的程式碼。

您可以將 DAO 視為定義存取資料庫的自訂介面。

針對常見的資料庫作業,Room 程式庫可提供便利的註解,例如 @Insert@Delete@Update。除此之外,您還可以使用 @Query 註解。您可以編寫受 SQLite 支援的任何查詢。

另一個好處是,當您在 Android Studio 中建立查詢時,編譯器會檢查 SQL 查詢是否有語法錯誤。

對於睡眠追蹤器資料庫的睡眠夜數,您需要能夠執行以下操作:

  • 插入新的夜晚。
  • 更新現有夜間睡眠的結束時間和品質評分。
  • 根據索引鍵取得特定夜晚。
  • 取得所有晚數,以便顯示。
  • 取得最近一晚的睡眠資料
  • 刪除資料庫中的所有項目。

步驟 1:建立 SleepDatabase DAO

  1. database 套件中開啟 SleepDatabaseDao.kt
  2. 請注意,interface SleepDatabaseDao 已加上 @Dao 註解。所有 DAO 都必須以 @Dao 關鍵字註解。
@Dao
interface SleepDatabaseDao {}
  1. 在介面內文中,新增 @Insert 註解。在 @Insert 下方,新增 insert() 函式,以將 Entity 類別 SleepNight 的例項做為引數。

    就是這麼簡單!Room 會產生將 SleepNight 插入資料庫所需的所有程式碼。當您從 Kotlin 程式碼呼叫 insert() 時,Room 會執行 SQL 查詢,將實體插入資料庫中。(注意:您可以將函式命名為任何名稱)。
@Insert
fun insert(night: SleepNight)
  1. 為一個 SleepNight 新增帶有 update() 函式的 @Update 註解。更新的實體與傳入的實體金鑰相同。您可以更新實體的部分或所有其他屬性。
@Update
fun update(night: SleepNight)

剩餘功能沒有便利的註解,因此您必須使用 @Query 註解並提供 SQLite 查詢。

  1. 新增 @Query 註解,並加入 get() 函式,這個函式會採用 Long key 引數,並傳回可為空值的 SleepNight。系統會顯示缺少參數的錯誤。
@Query
fun get(key: Long): SleepNight?
  1. 查詢以字串參數的形式提供給註解。在 @Query 中新增參數。將其設為 SQLite 查詢的 String
  • daily_sleep_quality_table 中選取所有欄
  • WHERE nightId 與 :key 引數相符。

    請注意 :key。您可以在查詢中使用冒號標記法來參照函式中的引數。
("SELECT * from daily_sleep_quality_table WHERE nightId = :key")
  1. 新增另一個 @Query,其中包含 clear() 函式和 SQLite 查詢,用於從 daily_sleep_quality_table DELETE 所有項目。這項查詢不會刪除資料表本身,

    @Delete 註解會刪除一個項目,您可以使用 @Delete 並提供要刪除的晚數清單。缺點是您必須擷取或瞭解表格內容。@Delete 註解很適合刪除特定項目,但如果想清除資料表中的所有項目,效率就不高。
@Query("DELETE FROM daily_sleep_quality_table")
fun clear()
  1. 新增具有 getTonight() 函式的 @Query。將 getTonight() 傳回的 SleepNight 設為可為空值,以便函式處理資料表為空的情況。(資料表一開始是空的,資料清除後也是空的)。

    如要從資料庫取得「今晚」,請編寫 SQLite 查詢,傳回依 nightId 遞減排序的結果清單中第一個元素。使用 LIMIT 1 只傳回一個元素。
@Query("SELECT * FROM daily_sleep_quality_table ORDER BY nightId DESC LIMIT 1")
fun getTonight(): SleepNight?
  1. 新增具有 getAllNights() 函式的 @Query
  • 讓 SQLite 查詢傳回 daily_sleep_quality_table 中的所有資料欄,以遞減順序排序。
  • getAllNights()SleepNight 實體清單做為 LiveData 傳回。Room 將隨時為您更新這個 LiveData,因此您只需明確取得資料一次。
  • 您可能需要從 androidx.lifecycle.LiveData 匯入 LiveData
@Query("SELECT * FROM daily_sleep_quality_table ORDER BY nightId DESC")
fun getAllNights(): LiveData<List<SleepNight>>
  1. 雖然您看不到任何明顯的變更,但請執行應用程式,確定沒有任何錯誤。

在這項工作中,您要建立 Room 資料庫,並使用您在先前工作中建立的 Entity 和 DAO。

您需要建立抽象的資料庫容器類別,並加上 @Database 註解。這個類別包含一個方法,可在沒有資料庫時建立資料庫例項,或者傳回現有資料庫的參照。

取得 Room 資料庫的程序較為複雜,因此在開始編寫程式碼之前,請先瞭解一般程序:

  • 建立 public abstract 類別,即 extends RoomDatabase。這個類別會成為資料庫容器。這個類別是抽象的,因為 Room 會為您建立實作。
  • 為該類別加上 @Database 註解。在引數中,宣告資料庫的實體並設定版本號碼。
  • companion 物件內,定義傳回 SleepDatabaseDao 的抽象方法或屬性。Room 會為你生成內文。
  • 整個應用程式只需要一個 Room 資料庫執行個體,因此請將 RoomDatabase 設為單例模式。
  • 僅在資料庫不存在的情況下,使用 Room 的資料庫建構工具建立資料庫。否則,請傳回現有資料庫。

步驟 1:建立資料庫

  1. database 套件中開啟 SleepDatabase.kt
  2. 在檔案中,建立名為 SleepDatabaseabstract 類別,並擴充 RoomDatabase

    使用 @Database 為類別加上註解。
@Database()
abstract class SleepDatabase : RoomDatabase() {}
  1. 系統會顯示缺少實體和版本參數的錯誤。@Database 註解需要多個引數,以便 Room 能夠建構資料庫。
  • SleepNight 指定為包含 entities 清單的唯一項目。
  • version 設為 1每次變更結構定義時,都必須增加版本號碼。
  • 只要將 exportSchema 設為 false,即可不保留結構定義版本記錄的備份。
entities = [SleepNight::class], version = 1, exportSchema = false
  1. 資料庫需要知道該 DAO。在類別內文中,宣告一個傳回 SleepDatabaseDao 的抽象值。您可以擁有多個 DAO。
abstract val sleepDatabaseDao: SleepDatabaseDao
  1. 在下方定義 companion 物件。夥伴物件可讓用戶端存取建立或取得資料庫的方法,而不必例項化類別。由於這個類別的唯一用途是提供資料庫,因此沒有理由要例項化。
 companion object {}
  1. companion 物件中,宣告資料庫的私人空值變數 INSTANCE,並將其初始化為 nullINSTANCE 變數會在建立資料庫時保留對該資料庫的參照。這有助於避免重複開啟資料庫連線,因為這會耗費大量資源。

使用 @VolatileINSTANCE 加上註解。系統一律不會快取易失變數的值,所有讀取與寫入作業都會在主記憶體中完成。這有助於確保所有執行緒的 INSTANCE 值保持在最新狀態且相同。也就是說,任一執行緒對 INSTANCE 所做的變更會立即向所有其他執行緒顯示,您不會遇到兩個執行緒各自更新快取中的相同實體,進而造成問題的情況。

@Volatile
private var INSTANCE: SleepDatabase? = null
  1. INSTANCE 下方的 companion 物件內,使用資料庫建構工具所需的 Context 參數定義 getInstance() 方法。傳回類型 SleepDatabase。由於 getInstance() 尚未傳回任何內容,因此系統會顯示錯誤訊息。
fun getInstance(context: Context): SleepDatabase {}
  1. getInstance() 內新增 synchronized{} 區塊。傳入 this,即可存取內容。

    多個執行緒可能會同時要求一個資料庫例項,因而產生兩個資料庫,而非單一資料庫。這個問題不太可能發生在這個範例應用程式中,但較複雜的應用程式可能會遇到。如果將用來取得資料庫的程式碼納入 synchronized,就表示一次只有一個執行緒能夠進入此程式碼區塊,這可確保資料庫僅初始化一次。
synchronized(this) {}
  1. 在同步區塊內,將 INSTANCE 的目前值複製到本機變數 instance。這是為了善用「智慧型轉換」,這項功能僅適用於本機變數。
var instance = INSTANCE
  1. synchronized 區塊內,return instance 位於 synchronized 區塊的結尾。忽略傳回型別不符的錯誤;完成後,您絕不會傳回空值。
return instance
  1. return 陳述式上方,新增 if 陳述式,檢查 instance 是否為空值 (也就是目前沒有資料庫)。
if (instance == null) {}
  1. 如果 instancenull,請使用資料庫建構工具取得資料庫。在 if 陳述式的主體中,叫用 Room.databaseBuilder 並提供您傳入的內容、資料庫類別和資料庫名稱 sleep_history_database。如要移除這項錯誤,請按照下列步驟新增遷移策略和 build()
instance = Room.databaseBuilder(
                           context.applicationContext,
                           SleepDatabase::class.java,
                           "sleep_history_database")
  1. 將必要的遷移策略新增至建構工具。使用 .fallbackToDestructiveMigration().

    一般來說,您必須為遷移物件提供有關何時變更結構定義的遷移策略。「遷移物件」定義了如何擷取所有採用舊結構定義的資料列,並轉換為以新結構定義呈現的資料列,以免資料遺失。遷移不在本程式碼研究室的範圍內。其中一個簡單的解決方法是刪除並重新建構資料庫,但代表資料會遺失。
.fallbackToDestructiveMigration()
  1. 最後,呼叫 .build()
.build()
  1. if 陳述式中,將 INSTANCE = instance 指派為最後一個步驟。
INSTANCE = instance
  1. 最終程式碼應如下所示:
@Database(entities = [SleepNight::class], version = 1, exportSchema = false)
abstract class SleepDatabase : RoomDatabase() {

   abstract val sleepDatabaseDao: SleepDatabaseDao

   companion object {

       @Volatile
       private var INSTANCE: SleepDatabase? = null

       fun getInstance(context: Context): SleepDatabase {
           synchronized(this) {
               var instance = INSTANCE

               if (instance == null) {
                   instance = Room.databaseBuilder(
                           context.applicationContext,
                           SleepDatabase::class.java,
                           "sleep_history_database"
                   )
                           .fallbackToDestructiveMigration()
                           .build()
                   INSTANCE = instance
               }
               return instance
           }
       }
   }
}
  1. 建構並執行程式碼。

現在您擁有了使用 Room 資料庫所需的所有建構區塊。這個程式碼可以編譯並執行,但無法判斷它是否正常運作。因此,這正是新增一些基本測試的好時機。

步驟 2:測試 SleepDatabase

在這個步驟中,您將執行提供的測試,確認資料庫運作正常。這有助於確保資料庫正常運作,再進行建構。提供的測試是基本測試。如果是正式版應用程式,您會執行所有 DAO 中的所有函式和查詢。

入門應用程式包含 androidTest 資料夾。這個 androidTest 資料夾包含涉及 Android 檢測設備的單元測試,也就是說,測試需要 Android 架構,因此您必須在實體或虛擬裝置上執行測試。當然,您也可以建立及執行不涉及 Android 架構的純單元測試。

  1. 在 Android Studio 的 androidTest 資料夾中,開啟 SleepDatabaseTest 檔案。
  2. 如要取消程式碼註解,請選取所有註解程式碼,然後按下 Cmd+/Control+/ 鍵盤快速鍵。
  3. 查看檔案。

以下快速瀏覽測試程式碼,因為這是另一段可重複使用的程式碼:

  • SleepDabaseTest 是測試類別
  • @RunWith 註解會識別測試執行器,也就是設定及執行測試的程式。
  • 設定期間,系統會執行加上 @Before 註解的函式,並使用 SleepDatabaseDao 建立記憶體內 SleepDatabase。「記憶體內」表示這個資料庫不會儲存在檔案系統中,測試執行完畢後就會刪除。
  • 此外,建構記憶體內資料庫時,程式碼會呼叫另一個測試專用方法 allowMainThreadQueries。根據預設,如果您嘗試在主執行緒上執行查詢,系統會顯示錯誤訊息。這個方法可讓您在主執行緒上執行測試,但您只應在測試期間執行這項操作。
  • 在以 @Test 註解的測試方法中,您會建立、插入及擷取 SleepNight,並確認兩者相同。如有任何問題,請擲回例外狀況。在實際測試中,您會有許多 @Test 方法。
  • 測試完成後,標註 @After 的函式會執行,以關閉資料庫。
  1. 在「Project」窗格中的測試檔案上按一下滑鼠右鍵,然後選取「Run 'SleepDatabaseTest'」
  2. 測試執行完畢後,請在「SleepDatabaseTest」SleepDatabaseTest窗格中確認所有測試都已通過。

由於所有測試都通過,您現在瞭解以下幾件事:

  • 資料庫建立正確。
  • 您可以將 SleepNight 插入資料庫。
  • 你可以取回 SleepNight
  • SleepNight 具有正確的品質值。

Android Studio 專案:TrackMySleepQualityRoomAndTesting

測試資料庫時,您需要執行 DAO 中定義的所有方法。如要完成測試請新增並執行測試,以練習其他 DAO 方法。

  • 將資料表定義為具有 @Entity 註解的資料類別。定義具有 @ColumnInfo 註解的屬性做為資料表中的資料欄。
  • 定義資料存取物件 (DAO) 做為標有 @Dao 註解的介面。DAO 會將 Kotlin 函式對應至資料庫查詢。
  • 使用註解來定義 @Insert@Delete@Update 函式。
  • 針對其他查詢,使用 @Query 註解搭配 SQLite 查詢字串做為參數。
  • 建立具有 getInstance() 函式的抽象類別,該函式會傳回資料庫。
  • 使用檢測設備測試,確認資料庫和 DAO 運作正常。您可以將提供的測試做為範本。

Udacity 課程:

Android 開發人員說明文件:

其他說明文件和文章:

本節列出的作業可由課程講師指派給學習本程式碼研究室的學員。講師可自由採取以下行動:

  • 視需要指派作業。
  • 告知學員如何繳交作業。
  • 為作業評分。

講師可以視需求使用全部或部分建議內容,也可以自由指派任何其他合適的作業。

如果您是自行學習本程式碼研究室,不妨利用這些作業驗收學習成果。

回答問題

第 1 題

如何表示某個類別是儲存在 Room 資料庫中的實體?

  • 將類別擴充 DatabaseEntity
  • 使用 @Entity 為類別加上註解。
  • 使用 @Database 為類別加上註解。
  • 讓類別擴充 RoomEntity,並使用 @Room 為類別加上註解。

第 2 題

DAO (資料存取物件) 是 Room 用來將 Kotlin 函式對應至資料庫查詢的介面。

如何指出介面就是 Room 資料庫的 DAO?

  • 讓介面擴充 RoomDAO
  • 讓介面擴充 EntityDao,然後實作 DaoConnection() 方法。
  • 使用 @Dao 為介面加上註解。
  • 使用 @RoomConnection 為介面加上註解。

第 3 題

下列有關 Room 資料庫的敘述何者正確?(可複選)?

  • 您可以將 Room 資料庫的資料表定義為註解資料類別。
  • 如果您從查詢傳回 LiveDataRoom 會在 LiveData 變更時為您更新 LiveData
  • 每個 Room 資料庫都必須有一個 DAO,而且只能有一個。
  • 如要將類別識別為 Room 資料庫,請將其設為 RoomDatabase 的子類別,並使用 @Database 加上註解。

第 4 題

您可以在 @Dao 介面中使用下列哪些註解?(可複選)?

  • @Get
  • @Update
  • @Insert
  • @Query

第 5 題

如何驗證資料庫是否正常運作?請選取所有適用選項。

  • 編寫檢測設備測試。
  • 繼續編寫及執行應用程式,直到顯示資料為止。
  • 呼叫 Entity 類別中的類似方法,而不要呼叫 DAO 介面中的方法。
  • 執行 Room 程式庫提供的 verifyDatabase() 函式。

開始下一個課程:6.2 協同程式和 Room

如要查看本課程其他程式碼研究室的連結,請參閱 Android Kotlin 基礎知識程式碼研究室登陸頁面