Pesquisa e filtro do Android Room usando relações, consulta várias tabelas
Repo está abaixo, se você quiser emitir uma solicitação pull, inclua comentários.
Criei um banco de dados Room para meu PokemonApp. Quero poder filtrar e pesquisar o banco de dados com base no nome e nos tipos de Pokémon.
Eu tenho uma tabela para minha entidade Pokemon, uma tabela para a entidade PokemonType e uma tabela de junção para a entidade PokemonTypeJoin, também tenho uma classe de dados PokemonWithTypes que incorpora uma entidade Pokemon e define uma relação entre esta e uma lista de entidades PokemonType.
Entidade Pokémon:
@TypeConverters(RoomStringListConverter::class)
@Entity
data class Pokemon(
@NotNull
@PrimaryKey
@ColumnInfo(name = POKEMON_ID)
var id: Int,
@ColumnInfo(name = POKEMON_NAME)
var name: String,
@ColumnInfo(name = POKEMON_URL)
var url: String,
@ColumnInfo(name = POKEMON_WEIGHT)
val weight: Int,
@ColumnInfo(name = POKEMON_HEIGHT)
val height: Int,
@ColumnInfo(name = POKEMON_SPECIES)
var species: String,
@ColumnInfo(name = POKEMON_MOVES)
val moves: List<String>
)
const val POKEMON_ID: String = "pokemon_id"
const val POKEMON_NAME: String = "pokemon_name"
const val POKEMON_URL: String = "pokemon_url"
const val POKEMON_HEIGHT: String = "pokemon_height"
const val POKEMON_WEIGHT: String = "pokemon_weight"
const val POKEMON_MOVES: String = "pokemon_moves"
const val POKEMON_SPECIES: String = "pokemon_species"
Entidade PokemonType:
@Entity
data class PokemonType (
@NotNull
@PrimaryKey
@ColumnInfo(name = POKEMON_TYPE_ID)
var id: Int,
@ColumnInfo(name = POKEMON_TYPE_NAME)
var name: String,
@ColumnInfo(name = POKEMON_TYPE_SLOT)
var slot: Int
)
const val POKEMON_TYPE_ID: String = "type_id"
const val POKEMON_TYPE_NAME: String = "type_name"
const val POKEMON_TYPE_SLOT: String = "type_slot"
PokémonTypesJoin entidade:
@Entity(primaryKeys = [POKEMON_ID, POKEMON_TYPE_ID])
class PokemonTypesJoin(
@NotNull
@ColumnInfo(name = POKEMON_ID, index = true)
val pokemon_id: Int,
@NotNull
@ColumnInfo(name = POKEMON_TYPE_ID, index = true)
val pokemon_type_id: Int
)
const val POKEMON_ID: String = "id"
const val POKEMON_TYPE_ID: String = "type_id"
Classe PokemonWithTypes
data class PokemonWithTypes(
@Embedded
val pokemon: Pokemon,
@Relation(
parentColumn = Pokemon.POKEMON_ID,
entity = PokemonType::class,
entityColumn = PokemonType.POKEMON_TYPE_ID,
associateBy = Junction(
value = PokemonTypesJoin::class,
parentColumn = PokemonTypesJoin.POKEMON_ID,
entityColumn = PokemonTypesJoin.POKEMON_TYPE_ID
)
)
val types: List<PokemonType>
)
dada essa estrutura, posso obter e pesquisar por pokemon_name todos os PokemonWithTypes usando a seguinte consulta:
@Transaction
@Query("SELECT * FROM pokemon WHERE pokemon_name LIKE :search ORDER BY pokemon_id ASC")
fun getPokemonWithTypes(search: String?): LiveData<List<PokemonWithTypes>>
mas como posso agora adicionar um filtro (lista de string) que retorna apenas PokemonWithTypes onde qualquer um dos PokemonWithTypes.types corresponde a um determinado tipo na lista de filtros?
Então, dados 3 Pokémon (alguns dados removidos para brevidade)
PokemonWithTypes(pokemon=Pokemon(id=1, name=bulbasaur, types=[PokemonType(id=4, name=poison, slot=2), PokemonType(id=12, name=grass, slot=1)])
PokemonWithTypes(pokemon=Pokemon(id=4, name=charmander, types=[PokemonType(id=10, name=fire, slot=2), PokemonType(id=12, name=grass, slot=1)])
PokemonWithTypes(pokemon=Pokemon(id=7, name=squirtle, types=[PokemonType(id=11, name=water, slot=2), PokemonType(id=12, name=grass, slot=1)])
Atualmente, recebo todos os Pokémon e posso pesquisar por pokemon_name, mas gostaria de poder mostrar apenas os tipos de água ou apenas os tipos de grama, qualquer ideia é bem-vinda,
Eu tentei apenas filtrar uma string em vez de uma lista de strings com uma consulta como esta
@Transaction
@Query("SELECT * FROM pokemon, pokemonType WHERE type_name LIKE :filter AND pokemon_name LIKE :search ORDER BY pokemon_id ASC")
fun getPokemonWithTypes(search: String?, filter: String): LiveData<List<PokemonWithTypes>>
mas não funcionou
você pode conferir a coisa completa aquihttps://github.com/martipello/PokeApp/tree/add_filters
Respostas
Acho que a anotação @Relation não foi projetada para esse caso de uso. Ele foi projetado apenas para retornar TODOS os tipos relacionados, não um subconjunto filtrado. Acho que você tem 3 opções:
- Basta filtrá-lo com Kotlin:
pokemonWithTypes.filter { it.types.contains("GRASS") }
. Presumo que você não tenha mais de 10.000 registros de pokémons, então o desempenho não é um problema. - Escreva uma consulta de junção. Eu acho que é mais esforço para um ganho de desempenho insignificante.
- Use exibições de banco de dados conforme:https://issuetracker.google.com/issues/65509934. Isso é mais estático e você terá que escrever uma visão para cada tipo.