2023年11月2日 星期四

【Android Studio】 Room


添加依賴項

在專案層級的 build.gradle 檔案中,請在 ext 區塊中定義 room_version。
ext {
   kotlin_version = "1.6.20"
   nav_version = "2.4.1"
   room_version = '2.4.2'
}

在應用程式層級的 build.gradle 檔案中,請於依附元件清單的結尾新增下列依附元件。
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"

// optional - Kotlin Extensions and Coroutines support for Room
implementation "androidx.room:room-ktx:$room_version"
需要加上 :
plugins {
   ...
    id 'kotlin-kapt'
} 



簡介

ORM (Object Relational Mapping、物件關聯對映)

  • 將類別 (Classes) 對應到 資料表 (Tables)
  • 將物件 (Objects) 對應到 資料表中的一筆記錄 (Rows in a table)
  • 將屬性 (Attributes) 對應到資料表中的欄位 (Columns in a table)


實作

Entity 實體

在 Room 中,每個資料表會以一個類別表示,該類別在 Room 等 ORM 中,通常稱為「模型類別」或「實體」。系統會依此產生 SQLite 資料表。
在使用 Room 定義類別時,針對每個資料欄類型,只需考慮 Kotlin 類型,系統會自動將模型類別對應至資料庫使用的資料類型。

設計 Entity 

  • 新增 @ColumnInfo 註解來指定該欄的名稱。通常,與 Kotlin 屬性使用的 lowerCamelCase 不同,SQL 資料欄名稱的字詞以底線分隔。
  • 在 SQL 中,根據預設,資料欄的值可以是空值,但您可以視需要另外將值標示為非空值。這不同於 Kotlin 的運作方式,在 Kotlin 中,根據預設,值不可為空值。若不允許這個資料欄中的值為空值,請使用 @NonNull 註解加以標示。若非使用可空類型(比如String?),則即為不可空的,不必特別標註。
  • 時間會在資料庫中以整數表示。這是 Unix 時間戳記,可轉換成可用日期。雖然不同版本的 SQL 都提供了轉換日期的方式,但您仍可按照自己的需要使用 Kotlin 日期格式設定函式。
  • 可以選擇指定 @Entity(tableName="schedule"),但由於 Room 查詢不區分大小寫,因此可以不使用明確定義小寫的資料表名稱。
例子:
@Entity
data class Schedule (
    @PrimaryKey val id: Int,
    @ColumnInfo(name = "stop_name") val stopName: String,
    @ColumnInfo(name = "arrival_time") val arrivalTime: Int
)

DAO

在 DAO 上呼叫函式相當於在資料庫上執行 SQL 指令。
DAO 函式就像您在此應用程式中定義的函式一樣,通常會指定 SQL 指令,以便您準確指定希望該函式執行的操作。

例子 :


@Dao
interface ScheduleDao {
    @Query("SELECT * FROM schedule ORDER BY arrival_time ASC")
    fun getAll(): List<Schedule>

    @Query("SELECT * FROM schedule WHERE stop_name = :stopName ORDER BY arrival_time ASC")
    fun getByStopName(stopName: String): List<Schedule>
}

使用 ViewModel 當 「檢視資料模型」

圖片來源 : 簡介 Room 與 Flow (android.com)


例子 :
class BusScheduleViewModel(private val scheduleDao: ScheduleDao): ViewModel() {
    fun fullSchedule(): List<Schedule> = scheduleDao.getAll()

    fun scheduleForStopName(name: String): List<Schedule> = scheduleDao.getByStopName(name)
}

//由於 ViewModel 類別 BusScheduleViewModel 必須有生命週期感知特性,因此應以可回應生命週期事件的物件進行例項化。
//如果直接在其中一個片段中執行例項化,則片段物件必須處理一切 (包括所有記憶體管理工作),而這超出應用程式程式碼的工作範圍。
//您可以改為建立名為「工廠」的類別,該類別會替您將檢視畫面模型物件例項化。
class BusScheduleViewModelFactory(
    private val scheduleDao: ScheduleDao
) : ViewModelProvider.Factory {

//    您可以使用 BusScheduleViewModelFactory.create() 對 BusScheduleViewModelFactory 物件執行個體化,
//    這樣一來,您的檢視畫面模型便可具有生命週期感知特性,而無需片段直接處理這項工作。
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(BusScheduleViewModel::class.java)) {
            @Suppress("UNCHECKED_CAST")
            return BusScheduleViewModel(scheduleDao) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}


建立資料庫類別及預先填入資料庫

建立新的類別,負責 :
  1. 指定資料庫中定義的實體。
  2. 提供對各個 DAO 類別的單個執行個體的存取。
  3. 執行任何其他設定,例如預先填入資料庫。
例子 :
@Database(entities = [Schedule::class], version = 1)
abstract class AppDatabase: RoomDatabase() {
    abstract fun scheduleDao(): ScheduleDao

    companion object {
        @Volatile
        private var INSTANCE: AppDatabase? = null

        //使用 Elvis 運算子傳回資料庫的現有執行個體 (如果已有執行個體),或視需求首次建立資料庫
        fun getDatabase(context: Context): AppDatabase {
            return INSTANCE ?: synchronized(this) {
                val instance = Room.databaseBuilder(
                    context,
                    AppDatabase::class.java,
                    "app_database")
                    .createFromAsset("database/bus_schedule.db")
                    .build()
                INSTANCE = instance

                instance
            }
        }

    }
}

使用它

建立新的類別如下 :
class BusScheduleApplication : Application() {
    val database: AppDatabase by lazy { AppDatabase.getDatabase(this)}
}

並在 AndroidMainifest.xml 添加 :
<application
    android:name="com.example.busschedule.BusScheduleApplication"
    ...

在 Fragment 中使用 ViewModel

private val viewModel: BusScheduleViewModel by activityViewModels {
    BusScheduleViewModelFactory(
        (activity?.application as BusScheduleApplication).database.scheduleDao()
    )
}


0 comments:

張貼留言