I want to use room
I want to use a class for the insert method, one of the parameters of which is the title, which is ok, and the second one is a list of edit texts that ruin this work, when I call the insert method, the program crashes, what should I do, I don't know how to use a type converter. And this or what?
i use type converter and it is not working maybe the problem is my mine
how to use lists in room db android java
There are two primary ways, either
As SQLite is a relational database, then from a database approach, the latter would typically be used. However, from an object orientated approach the former is often used. The former is more likely to conform to normalisation. The latter tends to initially be simpler BUT can introduce complexities.
TypeConverters
If storing a representation of an unhandled type (object) then 2 TypeConverter annotated methods, in an interface or abstract class are required.
To
To try to represent an object as a numeric would be quite complex if at all even possible. so INTEGER and REAL can be ruled out.
A serialised representation could be stored as a BLOB, but typically this is not used.
Typically, as libraries exist, a JSON String representation is utilised.
Example
As an example consider an object that, consists of a Title and two lists,
TheObject
s ListSo a class and @Entity
annotated class (as it will be a table in the database) TheTable :-
@Entity
class TheTable {
@PrimaryKey
@NonNull
String title;
List<String> stringList;
ListOfObjects objectList;
TheTable(){}
@Ignore
TheTable(String title, List<String> stringList, ListOfObjects objectList) {
this.title = title;
this.stringList = stringList;
this.objectList = objectList;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public List<String> getStringList() {
return stringList;
}
public void setStringList(List<String> stringList) {
this.stringList = stringList;
}
public ListOfObjects getObjectList() {
return objectList;
}
public void setObjectList(ListOfObjects objectList) {
this.objectList = objectList;
}
}
@Ignore
supresses Room's warning about multiple constructors in this caseTo be able to store the 2 Lists, as explained, the MUST be converted via Type Converters. In this case the @TypeConverter
annotated methods are in the TypeConverters class:-
class TypeConverters {
private static final String separator = "~~";
@TypeConverter
public String convertFromListString(List<String> stringList) {
StringBuilder rv = new StringBuilder();
boolean afterFirst = false;
for (String s: stringList) {
if (afterFirst) rv.append(separator);
afterFirst = true;
rv.append(s);
}
return rv.toString();
}
@TypeConverter
public List<String> convertToString(String string) {
ArrayList<String> rv = new ArrayList<>();
boolean afterFirst = false;
for(String s: string.split(separator)) {
rv.add(s);
}
return rv;
}
@TypeConverter
public String convertFromTheObjectListToJSONString(ListOfObjects objectList) {
return new Gson().toJson(objectList);
}
@TypeConverter
public ListOfObjects convertFromJSONStringToObjectList(String JSONString) {
return new Gson().fromJson(JSONString,ListOfObjects.class);
}
}
As this answer comes from a bias towards the database aspect and thus that JSON is seen as wasteful. The due to the limited use the List of TheObject
is within a ListOfObjects
class as per:-
class ListOfObjects {
List<TheObject> objectList;
ListOfObjects(){}
@Ignore
ListOfObjects(List<TheObject> objectList) {
this.objectList = objectList;
}
public List<TheObject> getObjectList() {
return objectList;
}
public void setObjectList(List<TheObject> objectList) {
this.objectList = objectList;
}
}
As the answer shows both approaches and then that the TheObject
class has to suit both being a table and being a stored object then it is a little complex, as per:-
@Entity(primaryKeys = {"objectName","objectNumber"})
class TheObject {
@NonNull
String objectName;
long objectNumber;
String parentInTheTable = "";
TheObject(){}
@Ignore
TheObject(String objectName,long objectNumber){
this.objectName = objectName;
this.objectNumber = objectNumber;
}
@Ignore
TheObject(String objectName, long objectNumber, String parentInTheTable) {
this.objectName = objectName;
this.objectNumber = objectNumber;
this.parentInTheTable = parentInTheTable;
}
public long getObjectNumber() {
return objectNumber;
}
public String getObjectName() {
return objectName;
}
public void setObjectNumber(long objectNumber) {
this.objectNumber = objectNumber;
}
public void setObjectName(String objectName) {
this.objectName = objectName;
}
}
@Entity
so it can be a table in the database.@NonNull
annotation.@ColumnInfo
annotation creates an index on the parentInTheTable column as that would very likely improve efficiency. Note the the value is irrelevant/not required at all for the non-relational method and hence a default value of an empty string.To utilise either method then access to the database will be required and this is through a single @Dao
annotated interface TheDAOs
:-
@Dao
interface TheDAOs {
@Insert(onConflict = OnConflictStrategy.IGNORE)
long insert(TheTable theTable);
@Query("SELECT * FROM thetable")
List<TheTable> getAllTheTableRows();
/* For DB Relationship */
@Insert(onConflict = OnConflictStrategy.IGNORE)
long insert(TheObject theObject);
@Transaction /* coded to supress the Room warning */
@Query("SELECT * FROM thetable")
List<TheTableWithALLRelatedTheObjectsPOJO> getAllTheTablerowsWithTheRelatedTheObjects();
}
As can be seen another class TheTableWithALLRelatedTheObjectsPOJO
has appeared, to suit the relational database methodology. This is:-
class TheTableWithALLRelatedTheObjectsPOJO {
@Embedded
TheTable theTable;
@Relation(entity = TheObject.class,
parentColumn = "title",
entityColumn = "parentInTheTable")
List<TheObject> objectList;
}
TheObject
object is comprised of handled types (String-->TEXT and long-->INTEGER).All that remains is to bring this all together in an @Database
annotated abstract class AND for the non-relational database methodology is to introduce(inform Room where they are) the Type Converters.
@TypeConverters(listOfclasses)
is used. This can be coded at various places, the place defining the scope. Placed at the @Database
annotation offers the fullest scope.So the TheDatabase
abstract class is:-
@TypeConverters({a.a.so78349925javaroomlists.TypeConverters.class})
@Database(entities = {TheTable.class,TheObject.class},exportSchema = false,version = 1)
abstract class TheDatabase extends RoomDatabase {
abstract TheDAOs getTheDAOs();
private static volatile TheDatabase instance;
public static TheDatabase getInstance(Context context) {
if (instance==null) {
instance = Room.databaseBuilder(context,TheDatabase.class,"the_database.db")
.allowMainThreadQueries()
.build();
}
return instance;
}
}
.allowMainThreadQueries
has been used. This would not be used for a production App but simplifies the code.To put all the above actual use then some Activity code (a little long winded to go step by step through adding an retrieving data):-
public class MainActivity extends AppCompatActivity {
TheDatabase db;
TheDAOs dao;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
db = TheDatabase.getInstance(this); /* NOTE this does not create the database */
dao = db.getTheDAOs(); /* Database will be created WHEN an attempt is made to access the database i.e. here */
ArrayList<String> test = new ArrayList<>();
test.add("S1");
test.add("s2");
test.add("S3");
ArrayList<TheObject> testObjectList = new ArrayList<>();
testObjectList.add(new TheObject("MyFirstObject",100));
testObjectList.add(new TheObject("MySecondObject",200));
dao.insert(new TheTable("TITLE1",test,new ListOfObjects(testObjectList)));
/* Add the same list>TheObject> data via the related TheObject table */
for (TheObject to : testObjectList) {
dao.insert(new TheObject(to.objectName,to.objectNumber,"TITLE1"));
}
/* Some more Data (another set)*/
test.clear();
test.add("SS100");
test.add("SS200");
testObjectList.clear();
testObjectList.add(new TheObject("MyThirdObject",300));
testObjectList.add(new TheObject("MyFourthObject",400));
testObjectList.add(new TheObject("MyFifthObject",500));
dao.insert(new TheTable("Title2",test,new ListOfObjects(testObjectList)));
for (TheObject to: testObjectList) {
dao.insert(new TheObject(to.objectName,to.getObjectNumber(),"Title2"));
}
/* Extract the data non-relational methodology */
for (TheTable t: dao.getAllTheTableRows()) {
StringBuilder sb = new StringBuilder();
for (String s: t.stringList) {
sb.append("\n\tStringList is ").append(s);
}
StringBuilder sbo = new StringBuilder();
for (TheObject o: t.objectList.objectList) {
sbo.append("\n\tTheObject is ObjectName is ").append(o.objectName).append(" ObjectNumber is ").append(o.objectNumber);
}
Log.d("DBINFO","Title is " + t.title + ".\nThere are " + t.stringList.size() + " Strings. They are:-" + sb +
" : \n Additionally There are " + t.objectList.objectList.size() + " TheObjects. They are:- " + sbo);
}
/* Extract the data relational methodology */
for (TheTableWithALLRelatedTheObjectsPOJO ttwlrto: dao.getAllTheTablerowsWithTheRelatedTheObjects()) {
StringBuilder sb = new StringBuilder();
for (String s: ttwlrto.theTable.stringList) {
sb.append("\n\tStringList is ").append(s);
}
StringBuilder sbo = new StringBuilder();
for (TheObject o: ttwlrto.objectList) {
sbo.append("\n\tTheObject is ObjectName is ").append(o.objectName).append(" ObjectNumber is ").append(o.objectNumber);
}
Log.d("DBINFO","Title is " + ttwlrto.theTable.title + ".\nThere are " + ttwlrto.theTable.stringList.size() + " Strings. They are:-" + sb +
" : \n Additionally There are " + ttwlrto.objectList.size() + " TheObjects. They are:- " + sbo);
}
}
}
Results
When run the log includes:-
2024-04-19 11:17:42.628 D/DBINFO: Title is TITLE1.
There are 3 Strings. They are:-
StringList is S1
StringList is s2
StringList is S3 :
Additionally There are 2 TheObjects. They are:-
TheObject is ObjectName is MyFirstObject ObjectNumber is 100
TheObject is ObjectName is MySecondObject ObjectNumber is 200
2024-04-19 11:17:42.629 D/DBINFO: Title is Title2.
There are 2 Strings. They are:-
StringList is SS100
StringList is SS200 :
Additionally There are 3 TheObjects. They are:-
TheObject is ObjectName is MyThirdObject ObjectNumber is 300
TheObject is ObjectName is MyFourthObject ObjectNumber is 400
TheObject is ObjectName is MyFifthObject ObjectNumber is 500
2024-04-19 11:17:42.638 D/DBINFO: Title is TITLE1.
There are 3 Strings. They are:-
StringList is S1
StringList is s2
StringList is S3 :
Additionally There are 2 TheObjects. They are:-
TheObject is ObjectName is MyFirstObject ObjectNumber is 100
TheObject is ObjectName is MySecondObject ObjectNumber is 200
2024-04-19 11:17:42.638 D/DBINFO: Title is Title2.
There are 2 Strings. They are:-
StringList is SS100
StringList is SS200 :
Additionally There are 3 TheObjects. They are:-
TheObject is ObjectName is MyThirdObject ObjectNumber is 300
TheObject is ObjectName is MyFourthObject ObjectNumber is 400
TheObject is ObjectName is MyFifthObject ObjectNumber is 500
i.e. the results from either methodology are the same.
However, using App inspection, then the TheTable is:-
For the second relational methodology the highlighted data in TheTable would not be stored here, instead the data will be in the TheObject table :-
for the first methodology this table would not exist.
Note this has only dealt with a 1-n (one to many) type if relationship. n-n (many to many) relationships would typically utilise an associative table.