Android Sunflower核心组件:Room数据库设计与实现

Android Sunflower核心组件:Room数据库设计与实现

【免费下载链接】sunflower A gardening app illustrating Android development best practices with migrating a View-based app to Jetpack Compose. 【免费下载链接】sunflower 项目地址: https://gitcode.com/gh_mirrors/su/sunflower

一、Room数据库架构概述

Room是Android Jetpack组件库中的ORM(对象关系映射)框架,它简化了本地数据库操作,提供了编译时SQL验证和注解式数据访问接口。在Sunflower应用中,Room数据库承担着植物信息和种植记录的持久化存储职责,通过合理的实体设计和DAO(数据访问对象)接口,实现了高效的数据管理。

应用架构示意图

核心组件关系如下:

二、实体设计与关系映射

2.1 Plant实体

Plant实体对应植物信息表,存储植物的基本属性。使用@Entity注解标记,并通过@PrimaryKey指定主键。

@Entity(tableName = "plants")
data class Plant(
    @PrimaryKey @ColumnInfo(name = "id") val plantId: String,
    val name: String,
    val description: String,
    val growZoneNumber: Int,
    val wateringInterval: Int = 7, // 浇水间隔(天)
    val imageUrl: String = ""
) {
    // 判断是否需要浇水的业务逻辑
    fun shouldBeWatered(since: Calendar, lastWateringDate: Calendar) =
        since > lastWateringDate.apply { add(DAY_OF_YEAR, wateringInterval) }
}

2.2 GardenPlanting实体

GardenPlanting实体记录用户的种植信息,通过外键与Plant实体建立关联,使用@ForeignKey注解定义参照完整性约束。

@Entity(
    tableName = "garden_plantings",
    foreignKeys = [
        ForeignKey(
            entity = Plant::class, 
            parentColumns = ["id"], 
            childColumns = ["plant_id"]
        )
    ],
    indices = [Index("plant_id")]
)
data class GardenPlanting(
    @ColumnInfo(name = "plant_id") val plantId: String,
    @ColumnInfo(name = "plant_date") val plantDate: Calendar = Calendar.getInstance(),
    @ColumnInfo(name = "last_watering_date") val lastWateringDate: Calendar = Calendar.getInstance()
) {
    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "id")
    var gardenPlantingId: Long = 0
}

三、数据库配置与初始化

3.1 数据库类实现

AppDatabase类继承自RoomDatabase,通过@Database注解声明关联的实体类和数据库版本,并提供DAO接口的访问方法。

@Database(entities = [GardenPlanting::class, Plant::class], version = 1, exportSchema = false)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
    abstract fun gardenPlantingDao(): GardenPlantingDao
    abstract fun plantDao(): PlantDao

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

        fun getInstance(context: Context): AppDatabase {
            return instance ?: synchronized(this) {
                instance ?: buildDatabase(context).also { instance = it }
            }
        }

        private fun buildDatabase(context: Context): AppDatabase {
            return Room.databaseBuilder(context, AppDatabase::class.java, DATABASE_NAME)
                .addCallback(object : RoomDatabase.Callback() {
                    override fun onCreate(db: SupportSQLiteDatabase) {
                        super.onCreate(db)
                        // 数据库创建后执行初始化工作
                        val request = OneTimeWorkRequestBuilder<SeedDatabaseWorker>()
                            .setInputData(workDataOf(KEY_FILENAME to PLANT_DATA_FILENAME))
                            .build()
                        WorkManager.getInstance(context).enqueue(request)
                    }
                })
                .build()
        }
    }
}

3.2 数据库初始化

SeedDatabaseWorker是一个后台工作器,负责在数据库创建后从JSON文件导入初始植物数据。它通过AssetManager读取plants.json文件,并使用Gson解析为Plant对象列表,最后通过DAO接口批量插入数据库。

class SeedDatabaseWorker(context: Context, workerParams: WorkerParameters) : CoroutineWorker(context, workerParams) {
    override suspend fun doWork(): Result = withContext(Dispatchers.IO) {
        try {
            val filename = inputData.getString(KEY_FILENAME)
            if (filename != null) {
                applicationContext.assets.open(filename).use { inputStream ->
                    JsonReader(inputStream.reader()).use { jsonReader ->
                        val plantType = object : TypeToken<List<Plant>>() {}.type
                        val plantList: List<Plant> = Gson().fromJson(jsonReader, plantType)
                        
                        val database = AppDatabase.getInstance(applicationContext)
                        database.plantDao().upsertAll(plantList)
                        
                        Result.success()
                    }
                }
            } else {
                Result.failure()
            }
        } catch (ex: Exception) {
            Log.e(TAG, "Error seeding database", ex)
            Result.failure()
        }
    }
}

四、数据访问接口(DAO)设计

4.1 PlantDao接口

PlantDao提供植物信息的查询和批量插入操作,使用Room的注解式查询语法,支持协程和Flow响应式数据流。

@Dao
interface PlantDao {
    @Query("SELECT * FROM plants ORDER BY name")
    fun getPlants(): Flow<List<Plant>>

    @Query("SELECT * FROM plants WHERE growZoneNumber = :growZoneNumber ORDER BY name")
    fun getPlantsWithGrowZoneNumber(growZoneNumber: Int): Flow<List<Plant>>

    @Query("SELECT * FROM plants WHERE id = :plantId")
    fun getPlant(plantId: String): Flow<Plant>

    @Upsert
    suspend fun upsertAll(plants: List<Plant>)
}

4.2 GardenPlantingDao接口

GardenPlantingDao提供种植记录的增删查操作,特别实现了判断植物是否已种植和关联查询的功能。

@Dao
interface GardenPlantingDao {
    @Query("SELECT * FROM garden_plantings")
    fun getGardenPlantings(): Flow<List<GardenPlanting>>

    @Query("SELECT EXISTS(SELECT 1 FROM garden_plantings WHERE plant_id = :plantId LIMIT 1)")
    fun isPlanted(plantId: String): Flow<Boolean>

    @Transaction
    @Query("SELECT * FROM plants WHERE id IN (SELECT DISTINCT(plant_id) FROM garden_plantings)")
    fun getPlantedGardens(): Flow<List<PlantAndGardenPlantings>>

    @Insert
    suspend fun insertGardenPlanting(gardenPlanting: GardenPlanting): Long

    @Delete
    suspend fun deleteGardenPlanting(gardenPlanting: GardenPlanting)
}

五、类型转换器

由于Room不直接支持Calendar类型,Sunflower应用提供了Converters类,通过@TypeConverter注解实现Calendar与Long类型的相互转换。

class Converters {
    @TypeConverter
    fun calendarToDatestamp(calendar: Calendar): Long = calendar.timeInMillis

    @TypeConverter
    fun datestampToCalendar(value: Long): Calendar = 
        Calendar.getInstance().apply { timeInMillis = value }
}

六、数据库操作最佳实践

6.1 使用协程进行异步操作

所有DAO的写操作都使用suspend修饰符,确保在后台线程执行,避免阻塞UI线程。例如,PlantDetailViewModel中的种植操作:

viewModelScope.launch {
    gardenPlantingRepository.insertGardenPlanting(plantId)
}

6.2 使用Flow观察数据变化

DAO的查询方法返回Flow对象,允许UI层观察数据变化并自动更新界面。例如,PlantListViewModel中获取植物列表:

val plants: Flow<List<Plant>> = plantListRepository.getPlants()

6.3 数据库版本管理

当数据结构发生变化时,应通过增加数据库版本号并实现Migration来处理数据迁移,避免数据丢失。Sunflower当前使用版本1,如后续需要更新,可参考以下方式:

Room.databaseBuilder(context, AppDatabase::class.java, DATABASE_NAME)
    .addMigrations(MIGRATION_1_2)
    .build()

七、总结

Sunflower应用的Room数据库设计遵循了Android Jetpack的最佳实践,通过清晰的实体定义、高效的DAO接口和响应式数据流,实现了植物信息的持久化管理。这种架构不仅简化了数据操作代码,还确保了应用的性能和可维护性。

主要优势包括:

  • 类型安全的SQL操作,编译时验证
  • 减少样板代码,专注业务逻辑
  • 与Jetpack组件(如ViewModel、LiveData、WorkManager)无缝集成
  • 支持协程和Flow,简化异步数据处理

官方文档:CONTRIBUTING.md 完整代码:app/src/main/java/com/google/samples/apps/sunflower/data/

【免费下载链接】sunflower A gardening app illustrating Android development best practices with migrating a View-based app to Jetpack Compose. 【免费下载链接】sunflower 项目地址: https://gitcode.com/gh_mirrors/su/sunflower

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值