Repository: corona-warn-app/cwa-app-android
File: Corona-Warn-App/src/main/java/de/rki/coronawarnapp/familytest/core/storage/FamilyTestDatabase.kt (Kotlin)

Code before:
        null
    }

    @TypeConverter
    fun fromFamilyCoronaTest(test: FamilyCoronaTest): String {
        return gson.toJson(test)
    }
}

@Entity(tableName = "family_corona_test")
data class FamilyCoronaTestEntity(
    @PrimaryKey
    val identifier: TestIdentifier,
    @ColumnInfo(name = "test")
    val test: FamilyCoronaTest,
    @ColumnInfo(name = "moved_to_recycle_bin_at_millis")
    val movedToRecycleBinAtMillis: Long? = null,
)

@Dao
interface FamilyCoronaTestDao {

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insert(entity: FamilyCoronaTestEntity)

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertAll(entities: List<FamilyCoronaTestEntity>)

    @Query("SELECT * FROM family_corona_test WHERE identifier IN(:identifiers)")
    suspend fun getAll(identifiers: Set<TestIdentifier>): List<FamilyCoronaTestEntity?>

    @Delete
    suspend fun delete(entity: FamilyCoronaTestEntity)

    @Query("SELECT * FROM family_corona_test WHERE moved_to_recycle_bin_at_millis IS NULL")
    fun getAllActive(): Flow<List<FamilyCoronaTestEntity?>>

    @Query("SELECT * FROM family_corona_test WHERE moved_to_recycle_bin_at_millis IS NOT NULL")
    fun getAllInRecycleBin(): Flow<List<FamilyCoronaTestEntity?>>

    @Query("DELETE FROM family_corona_test")
    suspend fun deleteAll()

    @Query("SELECT * FROM family_corona_test WHERE identifier = :identifier")
    suspend fun get(identifier: TestIdentifier): FamilyCoronaTestEntity?

    @Transaction
    suspend fun update(identifier: TestIdentifier, update: suspend (FamilyCoronaTest) -> FamilyCoronaTest) {
        get(identifier)?.let {
            val updated = update(it.test).toEntity()
            insert(updated)

Review comment (suggestion):
We should consider (for the future) to not use insert with replace and write an upsert method. 
See this post for some details:  https://tech.bakkenbaeck.com/post/room-insert-update
I know we are not using foreign key constraint at the moment, but who knows, this could change in the future. (optional)

Code after:
        null
    }

    @TypeConverter
    fun fromFamilyCoronaTest(test: FamilyCoronaTest): String {
        return gson.toJson(test)
    }
}

@Entity(tableName = "family_corona_test")
data class FamilyCoronaTestEntity(
    @PrimaryKey
    val identifier: TestIdentifier,
    @ColumnInfo(name = "test")
    val test: FamilyCoronaTest,
    @ColumnInfo(name = "moved_to_recycle_bin_at_millis")
    val movedToRecycleBinAtMillis: Long? = null,
)

@Dao
interface FamilyCoronaTestDao {

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insert(entity: FamilyCoronaTestEntity)

    @Delete
    suspend fun delete(entity: FamilyCoronaTestEntity)

    @Query("SELECT * FROM family_corona_test WHERE moved_to_recycle_bin_at_millis IS NULL")
    fun getAllActive(): Flow<List<FamilyCoronaTestEntity?>>

    @Query("SELECT * FROM family_corona_test WHERE moved_to_recycle_bin_at_millis IS NOT NULL")
    fun getAllInRecycleBin(): Flow<List<FamilyCoronaTestEntity?>>

    @Query("DELETE FROM family_corona_test")
    suspend fun deleteAll()

    @Query("SELECT * FROM family_corona_test WHERE identifier = :identifier")
    suspend fun get(identifier: TestIdentifier): FamilyCoronaTestEntity?

    @Transaction
    suspend fun update(identifier: TestIdentifier, update: (FamilyCoronaTest) -> FamilyCoronaTest) {
        get(identifier)?.let {
            val updated = update(it.test).toEntity()
            if (it != updated) insert(updated)
        }
    }

    @Transaction
    suspend fun update(updates: List<Pair<TestIdentifier, (FamilyCoronaTest) -> FamilyCoronaTest>>) {
        updates.forEach {