Android CWB -> Parte 4[ Room ]

Carlos
4 min readSep 20, 2019

Olá, neste post, iremos dar sequência a nosso projeto de apresentação.

Os links anteriores são:

Parte 1: https://medium.com/@nicolaugalves/android-cwb-iniciando-projeto-c3a2c5670879

Parte 2: https://medium.com/@nicolaugalves/android-cwb-parte-2-fbf44ef004cf

Parte 3: https://medium.com/@nicolaugalves/android-cwb-parte-3-a0c856c22d8d

Paramos no uso do Coroutines anteriormente. Neste post vamos mudar a nossa chamada para buscar no banco de dados Room.

Vamos no gradle.app

Adicionar

apply plugin: 'kotlin-kapt'

e nas dependências

// Room components
implementation "androidx.room:room-runtime:2.1.0"
kapt "androidx.room:room-compiler:2.1.0"
androidTestImplementation "androidx.room:room-testing:2.1.0"

ao meu IGetUserDataSource

adicionei

fun getUserDB(): User

meu GetUserDataSource

override fun getUserDB(): User {
return User(true)
}

Agora vamos devolver pelo BD e não mais um User(true)

Criei as classes do BD no meu framework

Essa configuração é a seguinte.

AndroidCWBRoom é onde criamos nosso banco.

UserDao tem os acessos para os métodos que vai implementar ex: insert, delete, update, get. Fiz um exemplo também para uma chamada com Query, as vezes o programador se confunde com isso. Vai ai o help desta parte:

@Query(
"SELECT * from user WHERE token = :token AND didLogin = :didLogin")
fun getUserWithTokenAndDidLogin(token: String, didLogin: Boolean): List<UserEntity>

o UserEntity é diferente do User, pois ele pode ter informações que depois vão vir do endpoint então temos que fazer um mapeamento entre UserEntity e User. Por isso a classe UserEntitiyMapper.

A princípio o método fica igual criar uma instancia de User. Depois vou inserir um UserEntity no banco de dados local e fazer esse maper. Aguenta aí.

class UserEntityMapper {    companion object {
fun transformToUser(user: UserEntity?) : User {
return User(true)
}
}
}

E, para finalizar, precisamos colocar ele no AndroidCWBApplication na injeção. Agora você vai perceber que estou colocando 2 vezes o getDB() e isso é proposital. Assim que terminar esse exemplo vou implementar o MVVM com Factory para ele lidar com a criação das instancias.

private fun injectDependencies() {    AndroidCWBMvpFactory.inject(
getDb(),
AppDispatcherProvider().io(),
AppDispatcherProvider().ui(),
Interactors(
getUserUseCaseImpl()
)
)
}
private fun getUserUseCaseImpl(): GetUserUseCaseImpl {
return GetUserUseCaseImpl(
GetUserRepositoryImpl(
GetUserDataSource(
getDb()
)
)
)
}

Meu GetUserDataSource, ficou assim,

class GetUserDataSource(
private val db: AndroidCWBRoom
) : IGetUserDataSource {
override fun execute(username: String, password: String): User? {
return getUserDB()
}
override fun getUserDB(): User? {
val users = db.getUserDAO().getAllUser()
try {
if (users.isNotEmpty()) {
return UserEntityMapper.transformToUser(users.first())
} else {
if (insertUserEntityInDbAndReturnUser(users))
return UserEntityMapper.transformToUser(users.first()
)
}
} catch (e: Exception) {
throw Exception("Somenthing WRONG happens! hehe")
}
return null
}
private fun insertUserEntityInDbAndReturnUser(users: List<UserEntity>): Boolean {
var users1 = users
db.getUserDAO().insert(
UserEntity(didLogin = true, user = "AndroidCWB", token = "meutokenfalso")
)
users1 = db.getUserDAO().getAllUser() if (users1.isNotEmpty()) {
return true
}
return false
}
}

Explicando: Caso não tenha um User, insere, caso já tenha, usa esse mesmo.

E o meu teste atualizou, e continua passando, eee ta pensando o que? hehe.

class GetUserDataSourceTest {    private val dbRoom: AndroidCWBRoom = Mockito.mock(AndroidCWBRoom::class.java)
private val userDAO: UserDAO = Mockito.mock(UserDAO::class.java)
@Test
fun whenGetUserDataSourceCall_shoulCallDbRoom() {
Mockito.`when`(dbRoom.getUserDAO()).thenReturn(userDAO) val getUserDataSource = GetUserDataSource(
dbRoom
)
getUserDataSource.execute("", "")
Mockito.verify(dbRoom, Mockito.atLeast(1)).getUserDAO()
Mockito.verifyNoMoreInteractions(dbRoom)
}
}

É isso, agora vamo fazer o insert do User caso ele não tenha pelo menos 1 usuário no BD para podermos usar ele. Depois, lógico, vamos pegar esse usuário pela internet.

Assim, temos que criar nosso teste instrumentado para validar se o BD ta fazendo o insert mesmo.

Criando a classe no androidTest fica assim:

@RunWith(AndroidJUnit4::class)
class UserDAORoomTest {
private lateinit var androidCWBRoom: AndroidCWBRoom
private lateinit var userDAO: UserDAO
@Test
fun useAppContext() {
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
Assert.assertEquals("carlos.nicolau.galves.androidcwb", appContext.packageName)
}
@Before
fun createDb() {
val context = InstrumentationRegistry.getInstrumentation().targetContext
androidCWBRoom = AndroidCWBRoom.getDatabase(context)
userDAO = androidCWBRoom.getUserDAO()
}
@Test
fun whenInsertUser_checkIfDbHaveOneUser() {
androidCWBRoom.getUserDAO().insert(
UserEntity(didLogin = true, user = "AndroidCWB", token = "meutokenfalso")
)
Assert.assertNotNull(
userDAO.getAllUser()
)
}
@Test
fun whendeleteAlltUser_checkIfDbIsEmpty() {
androidCWBRoom.getUserDAO().deleteAll()
val list = userDAO.getAllUser()
Assert.assertTrue(list.isEmpty())
}
@After
fun closeDb() {
androidCWBRoom.close()
}
}

Nele estamos testando se realmente insere um UserEntity, depois vamos criando os demais testes, pegou a ideia né?

Também to verificando o excluir. Isso porque no meu código de produção, eu vou mostrar que na primeira vez, ele insere e retorna, e na segunda ele só pega o primeiro user. Com o teste integrado eu validei o delete para ajudar a chegar na solução de insert único.

Rodando os testes intrumentados o resultado

Estamos mantendo o controle de nosso código de produção.

Agora vamos validar o app para ver se adiciona um user, vou mostrar com o debug para onde ele aponta.

Primeira vez:

Nosso mapper acionado com 1 objeto já criado:

Agora na segunda vez:

Pronto..

Mais a frente, nós vamos criar um método para inserir os bonecos do android, com um json local. Mas vamos por parte que tudo terá seu tempo para ser demonstrado.

Bom, acho que é isso, no próximo capitulo vamos usar o ViewModel para ajudar a tratar os estados da tela. Vejo você lá! Valeu.

Att, Carlos Nicolau Galves.

--

--