Baza danych pokojów Android: jak obsłużyć Arraylist w encji?


90

Właśnie wdrożyłem Room do zapisywania danych offline. Ale w klasie Entity pojawia się następujący błąd:

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

A klasa jest następująca:

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

}

Więc w zasadzie chcę zapisać ArrayList w bazie danych, ale nie byłem w stanie znaleźć nic odpowiedniego dla niej. Czy możesz mi pomóc, jak zapisać tablicę za pomocą pokoju?

UWAGA: Klasa MyListItems Pojo zawiera 2 ciągi (na razie)

Z góry dziękuję.

Odpowiedzi:


83

Opcja nr 1: MyListItemsbądź taki @Entity, jaki MainActivityDatajest. MyListItemszałożyłby @ForeignKeypowrót do MainActivityData. W tym przypadku jednak MainActivityDatanie może mieć private ArrayList<MyListItems> myListItems, tak jak w Pokoju, byty nie odwołują się do innych bytów. Z modelem widoku lub podobną konstrukcją POJO może być jednak MainActivityDataskojarzony znak i ArrayList<MyListItems>.

Opcja nr 2: Skonfiguruj parę @TypeConvertermetod do konwertowania ArrayList<MyListItems>na i z niektórych typów podstawowych (np. A, na Stringprzykład przy użyciu JSON jako formatu przechowywania). Teraz MainActivityDatamożna mieć to ArrayList<MyListItems>bezpośrednio. Jednak nie będzie oddzielnej tabeli dla MyListItems, więc nie możesz MyListItemsbardzo dobrze wykonywać zapytań .


W porządku, dzięki za szybką odpowiedź. Spróbuję najpierw drugiej opcji (pierwsza opcja nie jest do końca oczywista, aby być tbh ..: E) i wrócę do ciebie.
Tushar Gogna

1
ArrayList -> String (używając Json) i na odwrót działało ładnie. Przy okazji, czy możesz też bardziej rozwinąć pierwszą opcję? Po prostu chcę poznać alternatywę. W każdym razie dzięki. :)
Tushar Gogna

@TusharGogna: Relacje są ujęte w dokumentacji Pokoju , a bit „encje nie odnoszą się bezpośrednio do innych bytów” jest również ujęty w dokumentacji Pokoju .
CommonsWare,

1
Tylko jako notatka. Jeśli na przykład zamierzasz utrwalić listę Int, musisz serializować ją jako ciąg dla opcji 2. To sprawia, że ​​zapytania są bardziej złożone. Wolałbym raczej wybrać opcję 1, ponieważ jest mniej zależna od typu.
axierjhtjz

6
W przyszłości może być konieczne sprawdzenie Twoich pozycji, więc zwykle wybieram opcję nr 1
Jeffrey

111

Konwerter typów został stworzony specjalnie do tego celu. W twoim przypadku możesz użyć fragmentu kodu podanego poniżej do przechowywania danych w DB.

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

I wspomnij o tej klasie w swoim DB pokoju w ten sposób

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

Więcej informacji tutaj


2
Czy ktoś może mi pomóc zrobić to samo w Kotlinie z Listą. W Javie działało dobrze. Ale kiedy przerobiłem go w Kolinie, nie działa
Ozeetee

2
Jak pytasz od tego arraylisty?
Sanjog Shrestha

@SanjogShrestha Nie rozumiem, co masz na myśli. Po prostu pobierasz arraylistę i wyszukujesz za pomocą metody get
Amit Bhandari

@AmitBhandari Weźmy powyższy scenariusz jako przykład. Chcę przeszukać tabelę (MainActivityData), w której zawiera myListItems (np. A, b, c), a userId to abc. Jak teraz piszemy zapytanie dla takiego przypadku?
Sanjog Shrestha

1
@bompf dzięki za sugestię. Chociaż ten przykład jest tylko ilustracją. Generalnie zawsze trzymamy jedną instancję gson na poziomie aplikacji.
Amit Bhandari

55

Wersja Kotlin do konwertera typów:

 class Converters {

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

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

Użyłem JobWorkHistoryprzedmiotu do swojego celu, użyj przedmiotu własnego

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

2
Myślę, że zamiast deserializacji do tablicy, a następnie przekonwertowania na List, lepiej jest użyć typu listy w następujący sposób: val listType = obiekt: TypeToken <List <JobWorkHistory>> () {} .type jak Amit wspomniany w odpowiedzi poniżej.
Sohayb Hassoun

3
Możesz również chcieć pobrać buforowaną Gsoninstancję z dowolnego miejsca w aplikacji. Inicjowanie nowej Gsoninstancji przy każdym połączeniu może być kosztowne.
Apsaliya

18

Lepsza wersja List<String>konwertera

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
Uważaj na używanie „,” jako separatora, ponieważ czasami twój ciąg może mieć ten sam znak i może to być bałagan.
emarshah

9

W ten sposób radzę sobie z konwersją listy

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

A potem w bazie danych robię, jak pokazano poniżej

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

A poniżej jest implementacja kotlin tego samego;

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

Używam tego rozwiązania dla prostych typów (np. List <Integer>, List <Long>), ponieważ jest lżejsze niż rozwiązania oparte na gson.
Julien Kronegg

2
W tym rozwiązaniu brakuje nieszczęśliwego przepływu (np. Zerowy i pusty ciąg, pusta lista).
Julien Kronegg

Tak, popełniłem błąd, kopiując wklejając to i straciłem co najmniej godzinę na tworzenie list pojedynczych elementów, tworząc elementy z pojedynczymi przecinkami. Przesłałem i odpowiedziałem z poprawką (w Kotlinie)
Daniel Wilson

6

Wystąpił ten sam komunikat o błędzie, jak opisano powyżej. Chciałbym dodać: jeśli otrzymujesz ten komunikat o błędzie w @Query, powinieneś dodać @TypeConverters nad adnotacją @Query.

Przykład:

@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
Próbowałem dodać @TypeConverters powyżej adnotacji zapytania, ale nadal otrzymuję ten sam błąd
zulkarnain shah

6

Osobiście odradzałbym @TypeConverters/ serializacje, ponieważ naruszają one zgodność normalnych formularzy bazy danych.

W tym konkretnym przypadku warto zdefiniować relację za pomocą adnotacji @Relation , która umożliwia odpytywanie zagnieżdżonych jednostek w jeden obiekt bez dodatkowej złożoności deklarowania a @ForeignKeyi ręcznego pisania wszystkich zapytań SQL:

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

Oprócz tego przykładu 1-N, możliwe jest również zdefiniowanie zależności 1-1 i NM.


Jedyna rozsądna odpowiedź tutaj! Nie naruszaj pierwszej normalnej formy!
Łukasz

4

Natywna wersja Kotlin wykorzystująca komponent serializacji Kotlin - kotlinx.serialization .

  1. Dodaj wtyczkę Gradle serializacji Kotlin i zależność do build.gradle:
apply plugin: 'kotlinx-serialization'

dependencies {
   ...
   implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1"
}
  1. Dodaj konwertery Type do swojej klasy Converter;
@TypeConverter
fun fromList(value : List<String>) = Json.encodeToString(value)

@TypeConverter
fun toList(value: String) = Json.decodeFromString<List<String>>(value)
  1. Dodaj klasę Converter do klasy bazy danych:
@TypeConverters(Converters::class)
abstract class YourDatabase: RoomDatabase() {...}

I jesteś skończony!

Dodatkowe zasoby:


3

Ta odpowiedź używa Kotina do dzielenia przecinkami i tworzenia ciągu oddzielonego przecinkami. Przecinek musi znajdować się na końcu wszystkich elementów oprócz ostatniego, więc będzie to obsługiwać również listy pojedynczych elementów.

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

w moim przypadku problem polegał na typie ogólnym opartym na tej odpowiedzi

https://stackoverflow.com/a/48480257/3675925 użyj listy zamiast 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)
     } 
}

nie trzeba dodawać @TypeConverters (IntArrayListConverter :: class) do wykonywania zapytań w klasie dao ani pól w klasie Entity i wystarczy dodać @TypeConverters (IntArrayListConverter :: class) do klasy bazy danych

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

0

Dodawanie @TypeConvertersz klasą konwertera jako params

do bazy danych i klasy Dao, sprawiło, że moje zapytania działały


1
czy możesz uściślić swoją odpowiedź?
K Pradeep Kumar Reddy

0

Konwersje JSON nie skalują się dobrze pod względem alokacji pamięci, wolałbym raczej użyć czegoś podobnego do powyższych odpowiedzi z pewną wartością zerową.

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

Dla prostych typów danych można zastosować powyższe, w przeciwnym razie dla złożonych typów danych Room zapewnia Embedded


0

Oto przykład dodawania typów customObject do tabeli DB pomieszczenia. https://mobikul.com/insert-custom-list-and-get-that-list-in-room-database-using-typeconverter/

Dodanie konwertera typów było łatwe, potrzebowałem tylko metody, która mogłaby zamienić listę obiektów w ciąg znaków, oraz metody, która mogłaby zrobić odwrotnie. Użyłem do tego gson.

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

Następnie dodałem adnotację do pola w Entity:

@TypeConverters(Converters.class)

public final ArrayList<MyListItem> myListItems;

0

Kiedy używamy TypaConverters, wtedy typ danych powinien być zwracany jako typ metody TypeConverter. Przykład Metoda TypeConverter Zwróć ciąg, a następnie dodanie tabeli COloum powinno być ciągiem

 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

Wszystkie powyższe odpowiedzi dotyczą listy ciągów. Ale poniżej pomoże ci znaleźć konwerter dla listy twoich obiektów.

W miejsce „ YourClassName ” dodaj klasę 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

Wszystkie powyższe odpowiedzi są prawidłowe. Tak, jeśli NAPRAWDĘ potrzebujesz przechowywać tablicę czegoś w jednym polu SQLite, TypeConverter jest rozwiązaniem.

I użyłem zaakceptowanej odpowiedzi w moich projektach.

Ale nie rób tego !!!

Jeśli potrzebujesz przechowywać tablicę w Entity w 90% przypadków, musisz utworzyć relacje jeden do wielu lub wiele do wielu.

W przeciwnym razie twoje następne zapytanie SQL do wybrania czegoś z kluczem wewnątrz tej tablicy będzie absolutnie piekło ...

Przykład:

Obiekt foo ma postać json: [{id: 1, name: "abs"}, {id: 2, name: "cde"}

Pasek obiektów: [{id, 1, foos: [1, 2], {...}]

Więc nie twórz jednostki takiej jak:

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

Zrób jak następny:

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

I bolą twoje foos: [] jako zapisy w tej tabeli.


nie rób założeń, jeśli yopu przechowywał listę identyfikatorów, które były dostępne w pierwszym wywołaniu API, ale nie w następnym, to z całą pewnością przechowuj te identyfikatory gdzieś, a następnie używaj ich do wysyłania zapytań do interfejsu API, przechowuj je w tabeli z tabelą skrzyżowań , to wykorzystuje oba rozwiązania, zgadzam się z tobą, że może to być postrzegane jako łatwe wyjście i nie jest świetne z wielu powodów
martinseal1987

-2

Użyj oficjalnego rozwiązania z pokoju, adnotacja @Embedded:

@Embedded(prefix = "mylist_array") private ArrayList<MyListItems> myListItems
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.