Поиск и фильтрация Android Room с использованием отношений, запрос нескольких таблиц

Aug 17 2020

Репо находится ниже, если вы хотите сделать пул-реквест, включите комментарии.

Я создал базу данных комнат для своего PokemonApp. Я хочу иметь возможность фильтровать и искать в базе данных на основе имени покемона и типов покемонов.

У меня есть таблица для моей сущности Pokemon, таблица для сущности PokemonType и таблица соединений для сущности PokemonTypeJoin, у меня также есть класс данных PokemonWithTypes, который встраивает сущность Pokemon и определяет связь между этим и списком сущностей PokemonType.

Сущность покемона:

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

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"

PokemonTypesJoin entity:

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

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

учитывая эту структуру, я могу получить и выполнить поиск по pokemon_name по всем типам PokemonWithTypes, используя следующий запрос:

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

но как я могу теперь добавить фильтр (список строк), который возвращает только PokemonWithTypes, когда любой из PokemonWithTypes.types соответствует заданному типу в списке фильтров?

Итак, даны 3 покемона (некоторые данные удалены для краткости)

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

В настоящее время я получаю всех покемонов и могу выполнять поиск по pokemon_name, но я хотел бы иметь возможность отображать только типы воды или только типы травы, любые идеи приветствуются,

Я пробовал просто фильтровать строку вместо списка строк с таким запросом

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

но это не сработало

вы можете проверить полную здесь https://github.com/martipello/PokeApp/tree/add_filters

Ответы

1 CarsonHolzheimer Aug 17 2020 at 08:05

Я думаю, что аннотация @Relation не предназначена для этого варианта использования. Он предназначен только для возврата ВСЕХ связанных типов, а не отфильтрованного подмножества. Думаю, у вас есть 3 варианта:

  1. Просто профильтровать с Котлин: pokemonWithTypes.filter { it.types.contains("GRASS") }. Я предполагаю, что у вас не более 10000 записей покемонов, поэтому производительность не является проблемой.
  2. Напишите запрос на соединение. Я думаю, что это больше усилий для незначительного прироста производительности.
  3. Используйте представления базы данных согласно: https://issuetracker.google.com/issues/65509934. Это более статично, и вам придется написать представление для каждого типа.