Cerca e filtra Android Room utilizzando le relazioni, interroga più tabelle

Aug 17 2020

Repo è sotto se si desidera emettere una richiesta pull, si prega di includere commenti.

Ho creato un database Room per la mia PokemonApp, voglio essere in grado di filtrare e cercare nel database in base al nome e al tipo di Pokemon.

Ho una tabella per la mia entità Pokemon, una tabella per l'entità PokemonType e una tabella di giunzione per l'entità PokemonTypeJoin, ho anche una classe di dati PokemonWithTypes che incorpora un'entità Pokemon e definisce una relazione tra questa e un elenco di entità PokemonType.

Entità 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"

Entità 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"

Entità PokemonTypesJoin:

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

data questa struttura posso ottenere e cercare per pokemon_name tutti i PokemonWithTypes usando la seguente query:

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

ma come posso ora aggiungere un filtro (elenco di stringhe) che restituisce solo PokemonWithTypes dove uno qualsiasi dei PokemonWithTypes.types corrisponde a un determinato tipo nell'elenco dei filtri?

Quindi dato 3 Pokemon (alcuni dati rimossi per brevità)

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

Al momento ottengo tutti i Pokemon e posso cercare per pokemon_name, ma mi piacerebbe essere in grado di mostrare solo i tipi di acqua o solo i tipi di erba, qualsiasi idea è benvenuta,

Ho provato a filtrare solo su una stringa anziché su un elenco di stringhe con una query come questa

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

ma non ha funzionato

puoi controllare l'intera cosa quihttps://github.com/martipello/PokeApp/tree/add_filters

Risposte

1 CarsonHolzheimer Aug 17 2020 at 08:05

Penso che l'annotazione @Relation non sia progettata per quel caso d'uso. È progettato solo per restituire TUTTI i tipi correlati, non un sottoinsieme filtrato. Penso che tu abbia 3 opzioni:

  1. Basta filtrarlo con Kotlin: pokemonWithTypes.filter { it.types.contains("GRASS") }. Presumo che tu non abbia più di 10000 record di pokemon, quindi le prestazioni non sono un problema.
  2. Scrivi una query di unione. Penso che sia uno sforzo maggiore per un guadagno di prestazioni trascurabile.
  3. Usa le visualizzazioni del database come da:https://issuetracker.google.com/issues/65509934. Questo è più statico e dovrai scrivere una vista per ogni tipo.