Base de datos de salas de Android: ¿Cómo manejar Arraylist en una entidad?


84

Acabo de implementar Room para guardar datos sin conexión. Pero en una clase de entidad, recibo el siguiente error:

Error:(27, 30) error: Cannot figure out how to save this field into database. You can consider adding a type converter for it.

Y la clase es la siguiente:

@Entity(tableName = "firstPageData")
public class MainActivityData {

    @PrimaryKey
    private String userId;

    @ColumnInfo(name = "item1_id")
    private String itemOneId;

    @ColumnInfo(name = "item2_id")
    private String itemTwoId;

    // THIS IS CAUSING THE ERROR... BASICALLY IT ISN'T READING ARRAYS
    @ColumnInfo(name = "mylist_array")
    private ArrayList<MyListItems> myListItems;

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public ArrayList<MyListItems> getMyListItems() {
        return myListItems;
    }

    public void setCheckListItems(ArrayList<MyListItems> myListItems) {
        this.myListItems = myListItems;
    }

}

Básicamente, quiero guardar ArrayList en la base de datos, pero no pude encontrar nada relevante. ¿Puede orientarme sobre cómo guardar una matriz usando Room?

NOTA: La clase MyListItems Pojo contiene 2 cadenas (a partir de ahora)

Gracias por adelantado.

Respuestas:


78

Opción n. ° 1: MyListItemsser un@Entity tal como MainActivityDataestá. MyListItemsestablecería una @ForeignKeyespalda a MainActivityData. En este caso, sin embargo, MainActivityDatano puede haber private ArrayList<MyListItems> myListItems, como en Room, las entidades no se refieren a otras entidades. Sin embargo, un modelo de vista o una construcción POJO similar podría tener un MainActivityDatay su asociado ArrayList<MyListItems>.

Opción n. ° 2: configure un par de @TypeConvertermétodos para convertir ArrayList<MyListItems>desde y hacia algún tipo básico (por ejemplo, a String, como usar JSON como formato de almacenamiento). Ahora, MainActivityDatapuede tener suArrayList<MyListItems> directamente. Sin embargo, no habrá una tabla separada para MyListItems, por lo que no puede consultar MyListItemsmuy bien.


Muy bien, gracias por la rápida respuesta. Intentaré la segunda opción primero (la primera opción no está del todo clara para ser tbh ..: E) y me pondré en contacto contigo.
Tushar Gogna

1
ArrayList -> String (usando Json) y viceversa funcionó muy bien. Por cierto, ¿puedes elaborar más la 1ª opción? Solo quiero saber la alternativa. Gracias de cualquier manera. :)
Tushar Gogna

@TusharGogna: Las relaciones se tratan en la documentación de Room , y el bit "las entidades no se refieren directamente a otras entidades" también se cubre en la documentación de Room .
CommonsWare

1
Solo como una nota. Si va a conservar una lista de Int, por ejemplo, debe serializarla como cadena para la opción 2. Esto hace que las consultas sean más complejas. Prefiero optar por la opción 1 ya que depende menos del "tipo".
axierjhtjz

5
En algún momento en el futuro, es posible que deba consultar sus artículos, por lo que generalmente elegiré la opción n. ° 1
Jeffrey

107

Convertidor de tipo están hechos específicamente para eso. En su caso, puede utilizar el fragmento de código que se proporciona a continuación para almacenar datos en la base de datos.

public class Converters {
    @TypeConverter
    public static ArrayList<String> fromString(String value) {
        Type listType = new TypeToken<ArrayList<String>>() {}.getType();
        return new Gson().fromJson(value, listType);
    }

    @TypeConverter
    public static String fromArrayList(ArrayList<String> list) {
        Gson gson = new Gson();
        String json = gson.toJson(list);
        return json;
    }
}

Y menciona esta clase en tu Room DB así

@Database (entities = {MainActivityData.class},version = 1)
@TypeConverters({Converters.class})

Más info aquí


2
¿Alguien puede ayudarme a hacer lo mismo en Kotlin con List? En Java estaba funcionando bien. Pero cuando lo convertí en Kolin, no funciona
Ozeetee

2
¿Cómo consulta desde esa lista de matrices?
Sanjog Shrestha

@SanjogShrestha No entiendo qué quieres decir. Simplemente recupera la lista de matrices y la consulta usando el método get
Amit Bhandari

@AmitBhandari Tomemos el escenario anterior como ejemplo. Quiero buscar en la tabla (MainActivityData) donde myListItems contiene (por ejemplo, a, b, c) y userId es abc. Ahora, ¿cómo escribimos una consulta para tal caso?
Sanjog Shrestha

1
@bompf gracias por la sugerencia. Aunque este ejemplo es solo una ilustración. Generalmente, siempre mantenemos una instancia de gson a nivel de aplicación.
Amit Bhandari

51

Versión Kotlin para convertidor de tipo:

 class Converters {

    @TypeConverter
    fun listToJson(value: List<JobWorkHistory>?) = Gson().toJson(value)

    @TypeConverter
    fun jsonToList(value: String) = Gson().fromJson(value, Array<JobWorkHistory>::class.java).toList()
}

Usé el JobWorkHistoryobjeto para mi propósito, usa el objeto tuyo

@Database(entities = arrayOf(JobDetailFile::class, JobResponse::class), version = 1)
@TypeConverters(Converters::class)
abstract class MyRoomDataBase : RoomDatabase() {
     abstract fun attachmentsDao(): AttachmentsDao
}

2
Creo que en lugar de deserializar a una matriz y luego convertir a List, es mejor usar un tipo de List como este: val listType = object: TypeToken <List <JobWorkHistory>> () {} .type como Amit mencionado en la respuesta a continuación.
Sohayb Hassoun

3
Además, es posible que desee obtener una Gsoninstancia almacenada en caché desde algún lugar de su aplicación. Inicializar una nueva Gsoninstancia en cada llamada puede resultar costoso.
Apsaliya

14

Mejor versión del List<String>convertidor

class StringListConverter {
    @TypeConverter
    fun fromString(stringListString: String): List<String> {
        return stringListString.split(",").map { it }
    }

    @TypeConverter
    fun toString(stringList: List<String>): String {
        return stringList.joinToString(separator = ",")
    }
}

6
Tenga cuidado de usar "," como separador, ya que a veces su cadena puede tener el mismo carácter y puede ser un desastre.
emarshah

9

Así es como manejo la conversión de listas

public class GenreConverter {
@TypeConverter
public List<Integer> gettingListFromString(String genreIds) {
    List<Integer> list = new ArrayList<>();

    String[] array = genreIds.split(",");

    for (String s : array) {
       if (!s.isEmpty()) {
           list.add(Integer.parseInt(s));
       }
    }
    return list;
}

@TypeConverter
public String writingStringFromList(List<Integer> list) {
    String genreIds = "";
    for (int i : list) {
        genreIds += "," + i;
    }
    return genreIds;
}}

Y luego en la base de datos hago lo que se muestra a continuación

@Database(entities = {MovieEntry.class}, version = 1)
@TypeConverters(GenreConverter.class)

Y a continuación se muestra una implementación de kotlin del mismo;

class GenreConverter {
@TypeConverter
fun gettingListFromString(genreIds: String): List<Int> {
    val list = mutableListOf<Int>()

    val array = genreIds.split(",".toRegex()).dropLastWhile {
        it.isEmpty()
    }.toTypedArray()

    for (s in array) {
        if (s.isNotEmpty()) {
            list.add(s.toInt())
        }
    }
    return list
}

@TypeConverter
fun writingStringFromList(list: List<Int>): String {
    var genreIds=""
    for (i in list) genreIds += ",$i"
    return genreIds
}}

Utilizo esta solución para tipos simples (por ejemplo, List <Integer>, List <Long>) porque es más ligero que las soluciones basadas en gson.
Julien Kronegg

2
Esta solución pierde el flujo infeliz (por ejemplo, cadena nula y vacía, lista nula).
Julien Kronegg

Sí, cometí el error de copiar y pegar esto y perdí al menos una hora en listas de elementos individuales creando elementos con comas simples. Envié y respondo con una solución para eso (en Kotlin)
Daniel Wilson

6

Tenía el mismo mensaje de error descrito anteriormente. Me gustaría agregar: si recibe este mensaje de error en una @Query, debe agregar @TypeConverters encima de la anotación @Query.

Ejemplo:

@TypeConverters(DateConverter.class)
@Query("update myTable set myDate=:myDate  where id = :myId")
void updateStats(int myId, Date myDate);

....

public class DateConverter {

    @TypeConverter
    public static Date toDate(Long timestamp) {
        return timestamp == null ? null : new Date(timestamp);
    }

    @TypeConverter
    public static Long toTimestamp(Date date) {
        return date == null ? null : date.getTime();
    }
}

1
Intenté agregar @TypeConverters encima de la anotación de consulta, pero sigo recibiendo el mismo error
zulkarnain shah

2

Esta respuesta usa Kotin para dividir por comas y construir la cadena delineada por comas. La coma debe ir al final de todos los elementos excepto el último, por lo que también manejará listas de elementos individuales.

object StringListConverter {
        @TypeConverter
        @JvmStatic
        fun toList(strings: String): List<String> {
            val list = mutableListOf<String>()
            val array = strings.split(",")
            for (s in array) {
                list.add(s)
            }
            return list
        }

        @TypeConverter
        @JvmStatic
        fun toString(strings: List<String>): String {
            var result = ""
            strings.forEachIndexed { index, element ->
                result += element
                if(index != (strings.size-1)){
                    result += ","
                }
            }
            return result
        }
    }

2

en mi caso, el problema era de tipo genérico basado en esta respuesta

https://stackoverflow.com/a/48480257/3675925 use List en lugar de ArrayList

 import androidx.room.TypeConverter
 import com.google.gson.Gson 
 import com.google.gson.reflect.TypeToken
 class IntArrayListConverter {
     @TypeConverter
     fun fromString(value: String): List<Int> {
         val type = object: TypeToken<List<Int>>() {}.type
         return Gson().fromJson(value, type)
     }

     @TypeConverter
     fun fromArrayList(list: List<Int>): String {
         val type = object: TypeToken<List<Int>>() {}.type
         return Gson().toJson(list, type)
     } 
}

no es necesario agregar @TypeConverters (IntArrayListConverter :: class) para consultar en la clase dao ni campos en la clase Entity y simplemente agregar @TypeConverters (IntArrayListConverter :: class) a la clase de base de datos

@Database(entities = [MyEntity::class], version = 1, exportSchema = false)
@TypeConverters(IntArrayListConverter::class)
abstract class MyDatabase : RoomDatabase() {

2

Yo personalmente desaconsejaría @TypeConverters / serializaciones, ya que rompen el cumplimiento de formularios normales de la base de datos.

Para este caso en particular, podría valer la pena definir una relación usando la anotación @Relation , que permite consultar entidades anidadas en un solo objeto sin la complejidad adicional de declarar ay@ForeignKey escribir todas las consultas SQL manualmente:

@Entity
public class MainActivityData {
    @PrimaryKey
    private String userId;
    private String itemOneId;
    private String itemTwoId;
}

@Entity
public class MyListItem {
    @PrimaryKey
    public int id;
    public String ownerUserId;
    public String text;
}

/* This is the class we use to define our relationship,
   which will also be used to return our query results.
   Note that it is not defined as an @Entity */
public class DataWithItems {
    @Embedded public MainActivityData data;
    @Relation(
        parentColumn = "userId"
        entityColumn = "ownerUserId"
    )
    public List<MyListItem> myListItems;
}

/* This is the DAO interface where we define the queries.
   Even though it looks like a single SELECT, Room performs
   two, therefore the @Transaction annotation is required */
@Dao
public interface ListItemsDao {
    @Transaction
    @Query("SELECT * FROM MainActivityData")
    public List<DataWithItems> getAllData();
}

Aparte de este ejemplo 1-N, también es posible definir relaciones 1-1 y NM.


0

Añadiendo @TypeConverterscon la clase de convertidor como params

a la base de datos y a la clase Dao, hice que mis consultas funcionaran


1
¿Puedes elaborar tu respuesta?
K Pradeep Kumar Reddy

0

Las conversiones de Json no se escalan bien en términos de asignación de memoria. Prefiero optar por algo similar a las respuestas anteriores con cierta nulabilidad.

class Converters {
    @TypeConverter
    fun stringAsStringList(strings: String?): List<String> {
        val list = mutableListOf<String>()
        strings
            ?.split(",")
            ?.forEach {
                list.add(it)
            }

        return list
    }

    @TypeConverter
    fun stringListAsString(strings: List<String>?): String {
        var result = ""
        strings?.forEach { element ->
            result += "$element,"
        }
        return result.removeSuffix(",")
    }
}

Para tipos de datos simples, se puede usar lo anterior; de lo contrario, para tipos de datos complejos, Room proporciona Embedded


0

Este es el ejemplo para agregar los tipos customObject a la tabla Room DB. https://mobikul.com/insert-custom-list-and-get-that-list-in-room-database-using-typeconverter/

Agregar un convertidor de tipos fue fácil, solo necesitaba un método que pudiera convertir la lista de objetos en una cadena y un método que pudiera hacer lo contrario. Usé gson para esto.

public class Converters {

    @TypeConverter
    public static String MyListItemListToString(List<MyListitem> list) {
        Gson gson = new Gson();
        return gson.toJson(list);
    }

    @TypeConverter
    public static List<Integer> stringToMyListItemList(@Nullable String data) {
        if (data == null) {
            return Collections.emptyList();
        }

        Type listType = new TypeToken<List<MyListItem>>() {}.getType();

        Gson gson = new Gson();
        return gson.fromJson(data, listType);
    }
}

Luego agregué una anotación al campo en la Entidad:

@TypeConverters(Converters.class)

public final ArrayList<MyListItem> myListItems;

0

Cuando usamos TypaConverters, el tipo de datos debería ser el tipo de retorno del método TypeConverter. Ejemplo de método TypeConverter Return String y luego Adding Table COloum debe ser String

 private static final Migration MIGRATION_1_2 = new Migration(1, 2) {
    @Override
    public void migrate(@NonNull SupportSQLiteDatabase database) {
        // Since we didn't alter the table, there's nothing else to do here.
        database.execSQL("ALTER TABLE "+  Collection.TABLE_STATUS  + " ADD COLUMN deviceType TEXT;");
        database.execSQL("ALTER TABLE "+  Collection.TABLE_STATUS  + " ADD COLUMN inboxType TEXT;");
    }
};

0
 @Query("SELECT * FROM business_table")
 abstract List<DatabaseModels.Business> getBusinessInternal();


 @Transaction @Query("SELECT * FROM business_table")
 public ArrayList<DatabaseModels.Business> getBusiness(){
        return new ArrayList<>(getBusinessInternal());
 }

0

Todas las respuestas anteriores son para la lista de cadenas. Pero a continuación, le ayuda a encontrar un convertidor para la lista de sus objetos.

En lugar de " YourClassName ", agregue su clase Object.

 @TypeConverter
        public String fromValuesToList(ArrayList<**YourClassName**> value) {
            if (value== null) {
                return (null);
            }
            Gson gson = new Gson();
            Type type = new TypeToken<ArrayList<**YourClassName**>>() {}.getType();
            return gson.toJson(value, type);
        }
    
        @TypeConverter
        public ArrayList<**YourClassName**> toOptionValuesList(String value) {
            if (value== null) {
                return (null);
            }
            Gson gson = new Gson();
            Type type = new TypeToken<List<**YourClassName**>>() {
            }.getType();
            return gson.fromJson(value, type);
        }

0

Todas las respuestas anteriores son correctas. Sí, si REALMENTE necesita almacenar una matriz de algo en un campo SQLite, TypeConverter es una solución.

Y usé la respuesta aceptada en mis proyectos.

¡¡¡Pero no lo hagas !!!

Si necesita una matriz de almacenamiento en Entity en el 90% de los casos, debe crear relaciones de uno a muchos o de muchos a muchos.

De lo contrario, su próxima consulta SQL para seleccionar algo con clave dentro de esta matriz será absolutamente un infierno ...

Ejemplo:

El objeto foo viene como json: [{id: 1, name: "abs"}, {id: 2, name: "cde"}

Barra de objetos: [{id, 1, foos: [1, 2], {...}]

Así que no hagas una entidad como:

@Entity....
data class bar(
...
val foos: ArrayList<Int>)

Haz como a continuación:

@Entity(tablename="bar_foo", primaryKeys=["fooId", "barId"])
data class barFoo(val barId: Int, val fooId: Int)

Y lastime a tus foos: [] como registros en esta tabla.


no haga suposiciones si yopu estaba almacenando una lista de ID que estaba disponible en la primera llamada a la API pero no en la siguiente, entonces, por supuesto, almacene esos ID en algún lugar y luego úselos para consultar la API, guárdelo en una tabla con una tabla de unión , esto usa ambas soluciones, estoy de acuerdo con usted en que esto podría verse como una salida fácil y no es excelente por muchas razones
martinseal1987

0

Versión nativa de Kotlin que utiliza el componente de serialización de Kotlin: kotlinx.serialization .

  1. Agregue el complemento Gradle de serialización de Kotlin y la dependencia a su build.gradle:
apply plugin: 'kotlinx-serialization'

dependencies {
   ...
   implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1"
}
  1. Agregue los convertidores de tipo a su clase de convertidor;
@TypeConverter
fun fromList(value : List<String>) = Json.encodeToString(value)

@TypeConverter
fun toList(value: String) = Json.decodeFromString<List<String>>(value)
  1. Agregue su clase Converter a su clase de base de datos:
@TypeConverters(Converters::class)
abstract class YourDatabase: RoomDatabase() {...}

¡Y tu estas listo!

Recursos extra:


-2

Use la solución oficial de la sala, anotación @Embedded:

@Embedded(prefix = "mylist_array") private ArrayList<MyListItems> myListItems
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.