Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

02-room #6

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
apply plugin: 'shot'

android {
Expand Down Expand Up @@ -40,6 +41,9 @@ dependencies {
implementation "androidx.constraintlayout:constraintlayout:2.0.0-alpha3"
implementation "androidx.recyclerview:recyclerview:1.0.0"
implementation "androidx.lifecycle:lifecycle-runtime:2.0.0"
implementation "androidx.room:room-runtime:2.1.0-alpha04"
kapt "androidx.room:room-compiler:2.1.0-alpha04"
implementation "androidx.room:room-coroutines:2.1.0-alpha04"

/* Coroutines */
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.0"
Expand Down Expand Up @@ -71,6 +75,7 @@ dependencies {
}

androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.1.0"
testImplementation "androidx.room:room-testing:2.1.0-alpha04"
}

shot {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package com.karumi.jetpack.superheroes

import android.app.Application
import android.content.Context
import com.karumi.jetpack.superheroes.common.SuperHeroesDatabase
import com.karumi.jetpack.superheroes.data.repository.LocalSuperHeroDataSource
import com.karumi.jetpack.superheroes.data.repository.RemoteSuperHeroDataSource
import com.karumi.jetpack.superheroes.data.repository.SuperHeroRepository
import com.karumi.jetpack.superheroes.data.repository.room.SuperHeroDao
import org.kodein.di.Kodein
import org.kodein.di.KodeinAware
import org.kodein.di.android.androidModule
Expand All @@ -29,11 +31,18 @@ class SuperHeroesApplication : Application(), KodeinAware {

private fun appDependencies(): Kodein.Module {
return Kodein.Module("Application dependencies", allowSilentOverride = true) {
bind<SuperHeroesDatabase>() with singleton {
SuperHeroesDatabase.build(this@SuperHeroesApplication)
}
bind<SuperHeroDao>() with provider {
val database: SuperHeroesDatabase = instance()
database.superHeroesDao()
}
bind<SuperHeroRepository>() with provider {
SuperHeroRepository(instance(), instance())
}
bind<LocalSuperHeroDataSource>() with singleton {
LocalSuperHeroDataSource()
LocalSuperHeroDataSource(instance())
}
bind<RemoteSuperHeroDataSource>() with provider {
RemoteSuperHeroDataSource()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.karumi.jetpack.superheroes.common

import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import com.karumi.jetpack.superheroes.data.repository.room.SuperHeroDao
import com.karumi.jetpack.superheroes.data.repository.room.SuperHeroEntity

@Database(entities = [SuperHeroEntity::class], version = 2)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a neat way to test database migration would be nice to have it included, even if not required during the exercises.

https://developer.android.com/training/data-storage/room/migrating-db-versions#export-schema

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the PR is mid development, yesterday I started creating all tests and I'm using that approach, I only exported v2 schema because I did it late in the process but I'm adding schema-v1 as well

abstract class SuperHeroesDatabase : RoomDatabase() {
abstract fun superHeroesDao(): SuperHeroDao

companion object {
fun build(context: Context): SuperHeroesDatabase =
Room.databaseBuilder(context, SuperHeroesDatabase::class.java, "superheroes-db")
.addMigrations(from1To2)
.build()
}
}

private val from1To2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE superheroes")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was there a superheroes table before ?

database.execSQL(
"""
CREATE TABLE `superheroes` (
`id` TEXT NOT NULL,
`superhero_id` TEXT NOT NULL,
`superhero_name` TEXT NOT NULL,
`superhero_photo` TEXT,
`superhero_isAvenger` INTEGER NOT NULL DEFAULT 0,
`superhero_description` TEXT NOT NULL,
PRIMARY KEY(`id`)
)
"""
)
}
}
Original file line number Diff line number Diff line change
@@ -1,38 +1,30 @@
package com.karumi.jetpack.superheroes.data.repository

import com.karumi.jetpack.superheroes.data.repository.room.SuperHeroDao
import com.karumi.jetpack.superheroes.data.repository.room.SuperHeroEntity
import com.karumi.jetpack.superheroes.domain.model.SuperHero
import kotlinx.coroutines.delay

class LocalSuperHeroDataSource {
companion object {
private const val BIT_TIME = 250L
}

private val superHeroes: MutableMap<String, SuperHero> = mutableMapOf()
class LocalSuperHeroDataSource(
private val dao: SuperHeroDao
) {
suspend fun getAllSuperHeroes(): List<SuperHero> =
dao.getAll()
.map { it.toSuperHero() }

suspend fun getAllSuperHeroes(): List<SuperHero> {
waitABit()
return superHeroes.values.toList()
}

suspend fun get(id: String): SuperHero? {
waitABit()
return superHeroes[id]
}
suspend fun get(id: String): SuperHero? =
dao.getById(id)?.toSuperHero()

suspend fun saveAll(all: List<SuperHero>) {
waitABit()
superHeroes.clear()
superHeroes.putAll(all.associateBy { it.id })
dao.deleteAll()
dao.insertAll(all.map { it.toEntity() })
}

suspend fun save(superHero: SuperHero): SuperHero {
waitABit()
superHeroes[superHero.id] = superHero
dao.update(superHero.toEntity())
return superHero
}

private suspend fun waitABit() {
delay(BIT_TIME)
}
private fun SuperHeroEntity.toSuperHero(): SuperHero = superHero

private fun SuperHero.toEntity(): SuperHeroEntity = SuperHeroEntity(id, this)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.karumi.jetpack.superheroes.data.repository.room

import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update

@Dao
interface SuperHeroDao {
@Query("SELECT * FROM superheroes")
suspend fun getAll(): List<SuperHeroEntity>

@Query("SELECT * FROM superheroes WHERE id = :id")
suspend fun getById(id: String): SuperHeroEntity?

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

@Update
suspend fun update(superHero: SuperHeroEntity)

@Query("DELETE FROM superheroes")
suspend fun deleteAll()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.karumi.jetpack.superheroes.data.repository.room

import androidx.room.Embedded
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.karumi.jetpack.superheroes.domain.model.SuperHero

@Entity(tableName = "superheroes")
data class SuperHeroEntity(
@PrimaryKey val id: String,
@Embedded(prefix = "superhero_") val superHero: SuperHero
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems to me that Embedded is an advanced feature of Room, we could simplify this for the course, even if it's more verbose.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also not sure about the effect you want to have as SuperHero as already an id and the prefix of these fields.

the table will have
superheroes.id and superheroes. superhero_id

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually it was just a test to see how @Embedded work, in this case we had two copies of the same model, SuperHero and SuperHeroEntity, just wanted to try having the domain model stored in DB instead of a DB-only model.

If we go this way we can remove the outer id and configure the primary key from the @Entity annotation instead.

)