Android Room Database: Wie gehe ich mit Arraylist in einer Entität um?

84

Ich habe gerade Raum zum Speichern von Offline-Daten implementiert. In einer Entity-Klasse wird jedoch der folgende Fehler angezeigt:

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

Und die Klasse ist wie folgt:

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

}

Grundsätzlich möchte ich die ArrayList in der Datenbank speichern, konnte aber nichts Relevantes dafür finden. Können Sie mir zeigen, wie Sie ein Array mit Room speichern können?

HINWEIS: Die Pojo-Klasse MyListItems enthält 2 Strings (ab sofort).

Danke im Voraus.

Tushar Gogna
quelle

Antworten:

78

Option 1: Seien Sie MyListItemsein @Entity, wie es MainActivityDataist. MyListItemswürde einen @ForeignKeyRücken zu einrichten MainActivityData. In diesem Fall MainActivityDatakönnen private ArrayList<MyListItems> myListItemsEntitäten jedoch nicht wie in Room nicht auf andere Entitäten verweisen. Ein Ansichtsmodell oder ein ähnliches POJO-Konstrukt könnte jedoch ein MainActivityDataund das zugehörige ArrayList<MyListItems>haben.

Option 2: Richten Sie ein @TypeConverterMethodenpaar für die Konvertierung ArrayList<MyListItems>in und aus einem Basistyp ein (z. B. a String, z. B. mithilfe von JSON als Speicherformat). Jetzt MainActivityDatakann es ArrayList<MyListItems>direkt sein. Es wird jedoch keine separate Tabelle für geben MyListItems, sodass Sie nicht MyListItemssehr gut abfragen können .

CommonsWare
quelle
Okay, danke für die schnelle Antwort. Ich werde zuerst die 2. Option ausprobieren (die 1. Option ist nicht ganz klar, um tbh ..: E zu sein) und mich bei Ihnen melden.
Tushar Gogna
1
ArrayList -> String (mit Json) und umgekehrt hat gut funktioniert. Übrigens, können Sie auch die erste Option näher erläutern? Ich will nur die Alternative kennen. Danke trotzdem. :)
Tushar Gogna
@TusharGogna: Beziehungen werden in der Raumdokumentation behandelt , und das Bit "Entitäten verweisen nicht direkt auf andere Entitäten" wird auch in der Raumdokumentation behandelt .
CommonsWare
1
Nur als Notiz. Wenn Sie beispielsweise eine Liste von Int beibehalten möchten, müssen Sie sie als Zeichenfolge für Option 2 serialisieren. Dies macht Abfragen komplexer. Ich würde mich lieber für Option 1 entscheiden, da diese weniger vom Typ abhängig ist.
Axierjhtjz
5
Irgendwann in der Zukunft müssen Sie möglicherweise Ihre Artikel abfragen, damit ich normalerweise mit Option 1
Jeffrey
108

Typkonverter sind speziell dafür gemacht. In Ihrem Fall können Sie das unten angegebene Codefragment verwenden, um Daten in der Datenbank zu speichern.

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;
    }
}

Und erwähnen Sie diese Klasse in Ihrer Room DB so

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

Mehr Infos hier

Amit Bhandari
quelle
2
Kann mir jemand helfen, dasselbe in Kotlin mit List zu tun? In Java hat es gut funktioniert. Aber als ich es in Kolin umgebaut habe, funktioniert es nicht
Ozeetee
2
Wie fragen Sie von dieser Arrayliste ab?
Sanjog Shrestha
@ SanjogShrestha Ich verstehe nicht, was du meinst. Sie rufen einfach die Arrayliste ab und fragen mit der Methode get ab
Amit Bhandari
@AmitBhandari Nehmen wir das obige Szenario als Beispiel. Ich möchte die Tabelle (MainActivityData) durchsuchen, in der myListItems enthält (z. B. a, b, c) und userId abc ist. Wie schreiben wir nun eine Abfrage für einen solchen Fall?
Sanjog Shrestha
1
@bompf danke für den Vorschlag. Obwohl dieses Beispiel hier nur zur Veranschaulichung dient. Im Allgemeinen behalten wir immer eine gson-Instanz auf Anwendungsebene.
Amit Bhandari
51

Kotlin- Version für Typkonverter:

 class Converters {

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

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

Ich habe ein JobWorkHistoryObjekt für meinen Zweck verwendet, benutze das eigene Objekt

@Database(entities = arrayOf(JobDetailFile::class, JobResponse::class), version = 1)
@TypeConverters(Converters::class)
abstract class MyRoomDataBase : RoomDatabase() {
     abstract fun attachmentsDao(): AttachmentsDao
}
Manohar Reddy
quelle
2
Ich denke, anstatt in ein Array zu deserialisieren und dann in List zu konvertieren, ist es besser, einen Listentyp wie diesen zu verwenden: val listType = object: TypeToken <List <JobWorkHistory>> () {} .type wie Amit, wie in der Antwort unten erwähnt.
Sohayb Hassoun
3
Möglicherweise möchten Sie auch eine zwischengespeicherte GsonInstanz von einer beliebigen Stelle in Ihrer App abrufen. Das Initialisieren einer neuen GsonInstanz bei jedem Anruf kann teuer sein.
Apsaliya
14

Bessere Version des List<String>Konverters

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 = ",")
    }
}
Nitin Misra
quelle
6
Seien Sie vorsichtig, wenn Sie "," als Trennzeichen verwenden, da Ihre Zeichenfolge manchmal den gleichen Charakter hat und es zu einem Durcheinander kommen kann.
Emarshah
9

So gehe ich mit der Listenkonvertierung um

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;
}}

Und dann mache ich in der Datenbank wie unten gezeigt

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

Und unten ist eine Kotlin-Implementierung derselben;

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
}}
Derrick Njeru
quelle
Ich verwende diese Lösung für einfache Typen (z. B. List <Integer>, List <Long>), da sie leichter ist als die gson-basierten Lösungen.
Julien Kronegg
2
Diese Lösung verpasst den unglücklichen Fluss (zB null und leere Zeichenfolge, Nullliste).
Julien Kronegg
Ja, ich habe den Fehler gemacht, dies einzufügen, und mindestens eine Stunde an Listen einzelner Elemente verloren, in denen Elemente mit einzelnen Kommas erstellt wurden. Ich habe eingereicht und mit einem Fix dafür geantwortet (in Kotlin)
Daniel Wilson
6

Hatte die gleiche Fehlermeldung wie oben beschrieben. Ich möchte hinzufügen: Wenn Sie diese Fehlermeldung in einer @Query erhalten, sollten Sie @TypeConverters über der @Query-Annotation hinzufügen.

Beispiel:

@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();
    }
}
Live-Liebe
quelle
1
Ich habe versucht, @TypeConverters über der Abfrage-Annotation hinzuzufügen, aber ich
erhalte
2

Diese Antwort verwendet Kotin, um durch Komma zu teilen und die durch Kommas abgegrenzte Zeichenfolge zu konstruieren. Das Komma muss am Ende aller Elemente außer dem letzten stehen, damit auch einzelne Elementlisten behandelt werden.

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
        }
    }
Daniel Wilson
quelle
2

In meinem Fall war das Problem eine generische Typbasis auf dieser Antwort

https://stackoverflow.com/a/48480257/3675925 Verwenden Sie List anstelle von 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)
     } 
}

Es muss weder @TypeConverters (IntArrayListConverter :: class) hinzugefügt werden, um in der Dao-Klasse noch Felder in der Entity-Klasse abzufragen, sondern nur @TypeConverters (IntArrayListConverter :: class) zur Datenbankklasse hinzufügen

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

Ich würde persönlich davon abraten @TypeConverters / Serialisierungen , da sie die normale Formularkonformität der Datenbank verletzen.

In diesem speziellen Fall kann es sinnvoll sein , eine Beziehung mithilfe der Annotation @Relation zu definieren, mit der verschachtelte Entitäten in ein einzelnes Objekt abgefragt werden können, ohne die zusätzliche Komplexität, a zu deklarieren @ForeignKeyund alle SQL-Abfragen manuell zu schreiben:

@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();
}

Abgesehen von diesem 1-N-Beispiel können auch 1-1- und NM-Beziehungen definiert werden.

Albert Gomà
quelle
0

Hinzufügen @TypeConvertersmit der Konverterklasse als Parameter

Zur Datenbank und zur Dao-Klasse haben meine Abfragen funktioniert

Abitcode
quelle
1
Können Sie Ihre Antwort ausarbeiten?
K Pradeep Kumar Reddy
0

Json-Konvertierungen lassen sich in Bezug auf die Speicherzuweisung nicht gut skalieren. Ich würde lieber etwas Ähnliches wie die obigen Antworten mit einer gewissen Nullbarkeit wählen.

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(",")
    }
}

Für einfache Datentypen kann das Obige verwendet werden, andernfalls für komplexe Datentypen, die Room Embedded bereitstellt

Dokuzov
quelle
0

Hier ist das Beispiel zum Hinzufügen der customObject-Typen zur Room DB-Tabelle. https://mobikul.com/insert-custom-list-and-get-that-list-in-room-database-using-typeconverter/

Das Hinzufügen eines Typkonverters war einfach. Ich brauchte nur eine Methode, mit der die Liste der Objekte in eine Zeichenfolge umgewandelt werden kann, und eine Methode, mit der das Gegenteil möglich ist. Ich habe dafür gson verwendet.

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

Ich habe dann dem Feld in der Entität eine Anmerkung hinzugefügt:

@TypeConverters(Converters.class)

public final ArrayList<MyListItem> myListItems;
Prasanth.NVS
quelle
0

Wenn wir TypaConverters verwenden, sollte Datentyp der Typ der TypeConverter-Methode sein. Beispiel TypeConverter-Methode Return String, dann sollte das Hinzufügen von Table COloum String sein

 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;");
    }
};
jagadishlakkurcom jagadishlakk
quelle
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());
 }
Awscoolboy
quelle
0

Alle obigen Antworten beziehen sich auf eine Liste von Zeichenfolgen. Im Folgenden finden Sie jedoch Informationen zum Konverter für die Liste Ihrer Objekte.

Fügen Sie anstelle von " YourClassName " Ihre Object-Klasse hinzu.

 @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);
        }
Tarun A.
quelle
0

Alle Antworten oben richtig. Ja, wenn Sie WIRKLICH ein Array von etwas in einem SQLite-Feld speichern müssen, ist TypeConverter eine Lösung.

Und ich habe die akzeptierte Antwort in meinen Projekten verwendet.

Aber tu es nicht !!!

Wenn Sie in 90% der Fälle ein Speicherarray in Entity benötigen, müssen Sie Eins-zu-Viele- oder Viele-zu-Viele-Beziehungen erstellen.

Andernfalls ist Ihre nächste SQL-Abfrage zur Auswahl eines Schlüssels in diesem Array die Hölle ...

Beispiel:

Objekt foo kommt als json: [{id: 1, name: "abs"}, {id: 2, name: "cde"}

Objektleiste: [{id, 1, foos: [1, 2], {...}]

Machen Sie also keine Entität wie:

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

Machen Sie wie folgt:

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

Und wund deine foos: [] als Aufzeichnungen in dieser Tabelle.

w201
quelle
Machen Sie keine Annahmen, wenn yopu eine Liste von IDs speichert, die beim ersten API-Aufruf verfügbar waren, aber nicht beim nächsten. Speichern Sie diese IDs auf jeden Fall irgendwo und verwenden Sie sie dann, um die API abzufragen, speichern Sie sie in einer Tabelle mit einer Junction-Tabelle , dies verwendet beide Lösungen, ich stimme Ihnen zu, dass dies als einfacher Ausweg angesehen werden könnte und aus vielen Gründen nicht
großartig ist
0

Native Kotlin-Version mit Kotlins Serialisierungskomponente - kotlinx.serialization .

  1. Fügen Sie das Kotlin-Serialisierungs-Gradle-Plugin und die Abhängigkeit zu Ihrem build.gradle:
apply plugin: 'kotlinx-serialization'

dependencies {
   ...
   implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1"
}
  1. Fügen Sie die Typkonverter Ihrer Konverterklasse hinzu.
@TypeConverter
fun fromList(value : List<String>) = Json.encodeToString(value)

@TypeConverter
fun toList(value: String) = Json.decodeFromString<List<String>>(value)
  1. Fügen Sie Ihre Converter-Klasse zu Ihrer Datenbankklasse hinzu:
@TypeConverters(Converters::class)
abstract class YourDatabase: RoomDatabase() {...}

Und du bist fertig!

Zusätzliche Ressourcen:

Patrick
quelle
-2

Verwenden Sie die offizielle Lösung aus dem Raum, @Embedded Annotation:

@Embedded(prefix = "mylist_array") private ArrayList<MyListItems> myListItems
Fidan Bacaj
quelle