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ę.
źródło
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
źródło
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
JobWorkHistory
przedmiotu 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 }
źródło
Gson
instancję z dowolnego miejsca w aplikacji. Inicjowanie nowejGson
instancji przy każdym połączeniu może być kosztowne.Lepsza wersja
List<String>
konwerteraclass StringListConverter { @TypeConverter fun fromString(stringListString: String): List<String> { return stringListString.split(",").map { it } } @TypeConverter fun toString(stringList: List<String>): String { return stringList.joinToString(separator = ",") } }
źródło
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 }}
źródło
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(); } }
źródło
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
@ForeignKey
i 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.
źródło
Natywna wersja Kotlin wykorzystująca komponent serializacji Kotlin - kotlinx.serialization .
build.gradle
:apply plugin: 'kotlinx-serialization' dependencies { ... implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1" }
@TypeConverter fun fromList(value : List<String>) = Json.encodeToString(value) @TypeConverter fun toList(value: String) = Json.decodeFromString<List<String>>(value)
@TypeConverters(Converters::class) abstract class YourDatabase: RoomDatabase() {...}
I jesteś skończony!
Dodatkowe zasoby:
źródło
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 } }
źródło
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() {
źródło
Dodawanie
@TypeConverters
z klasą konwertera jako paramsdo bazy danych i klasy Dao, sprawiło, że moje zapytania działały
źródło
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
źródło
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;
źródło
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;"); } };
źródło
@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()); }
źródło
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); }
źródło
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.
źródło
Użyj oficjalnego rozwiązania z pokoju, adnotacja @Embedded:
@Embedded(prefix = "mylist_array") private ArrayList<MyListItems> myListItems
źródło