Búsqueda y filtro de habitaciones de Android usando relaciones, consulta varias tablas

Aug 17 2020

El repositorio se encuentra a continuación. Si desea emitir una solicitud de extracción, incluya comentarios.

Creé una base de datos de habitaciones para mi PokemonApp. Quiero poder filtrar y buscar en la base de datos según el nombre de Pokémon y los tipos de Pokémon.

Tengo una tabla para mi entidad Pokemon, una tabla para la entidad PokemonType y una tabla de unión para la entidad PokemonTypeJoin, también tengo una clase de datos PokemonWithTypes que incorpora una entidad Pokemon y define una relación entre esta y una lista de entidades PokemonType.

Entidad 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"

Entidad 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"

PokemonTypesÚnete a la entidad:

    @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"

Clase 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 esta estructura, puedo obtener y buscar por pokemon_name todos los PokemonWithTypes usando la siguiente consulta:

@Transaction
@Query("SELECT * FROM pokemon WHERE pokemon_name LIKE :search ORDER BY pokemon_id ASC")
fun getPokemonWithTypes(search: String?): LiveData<List<PokemonWithTypes>>

pero, ¿cómo puedo ahora agregar un filtro (lista de cadenas) que solo devuelva PokemonWithTypes donde cualquiera de PokemonWithTypes.types coincida con un tipo dado en la lista de filtros?

Entonces, dados 3 Pokémon (algunos datos eliminados por brevedad)

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)])

Actualmente tengo todos los Pokémon y puedo buscar por pokemon_name, pero me gustaría poder mostrar solo tipos de agua o solo tipos de hierba. Cualquier idea es bienvenida.

Intenté simplemente filtrar en una cadena en lugar de una lista de cadenas con una 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>>

pero no funcionó

puedes ver todo completo aquihttps://github.com/martipello/PokeApp/tree/add_filters

Respuestas

1 CarsonHolzheimer Aug 17 2020 at 08:05

Creo que la anotación @Relation no está diseñada para ese caso de uso. Solo está diseñado para devolver TODOS los tipos relacionados, no un subconjunto filtrado. Creo que tienes 3 opciones:

  1. Solo fíltralo con Kotlin: pokemonWithTypes.filter { it.types.contains("GRASS") }. Supongo que no tienes más de 10000 registros de pokemons, por lo que el rendimiento no es un problema.
  2. Escriba una consulta de combinación. Creo que eso es más esfuerzo para una ganancia de rendimiento insignificante.
  3. Usar vistas de base de datos según:https://issuetracker.google.com/issues/65509934. Esto es más estático y tendrás que escribir una vista para cada tipo.