How to use Android Room on entities without fields? - android

I have java objects which are are backed by a HashMap and thus do not have plain fields which can be discovered via reflection. For example:
public class City {
private final HashMap<String, String> internalMap = new HashMap<String, String>();
public String getId() {
return internalMap.get("id");
}
public void setId(String id) {
internalMap.put("id", id);
}
public String getName() {
return internalMap.get("name");
}
public void setName(String name) {
internalMap.put("name", name);
}
}
I want to use classes such as this an entity in Room without having to change its structure since I have many such classes which are auto-generated using code generation tools and there are specific reasons why they need to be backed by a HashMap. Each value of the HashMap should end up as a column in my database table (the key String is an internal implementation detail). Is it possible? Seems not to be at the moment due to how fields are discovered by the annotation processor.
UPDATE:
None of the answers were at all what I was looking for. I opened a new question without mentioning HashMap since that detail was not supposed to be relevant but all the answers latched on to it. See How to use Android Room with POJOs provided by an external library? for the updated question.

You can use TypeConverter for this.
For instance, you can use serialization provided by GSON to easily convert the map to serialized state and vice versa.
class MapConverter {
#TypeConverter
public String fromMap(HashMap<String, String> map) {
return new Gson().toJson(map);
}
#TypeConverter
public HashMap<String, String> fromString(String serializedMap) {
Type type = new TypeToken<HashMap<String, String>>(){}.getType();
return gson.fromJson("serializedMap", type);
}
}
And in your entity class:
#Entity
#TypeConverters({MapConverter.class})
public class CityEntity {
//...
private final HashMap<String, String> internalMap;
//...
}
So the converter will be available for this entity to serialize the hashmap to string and vice versa.
Gson is just a possible solution, you can actually use whatever you want.

Room has it's own set of annotations - and if you do not want to have a field mapped, you have to indicate this towards the annotation processor by annotating the POJO as it may be required; eg. #Entity without assigning a tableName, as well the #Ignore above the field to ignore:
#Entity
public class City {
#Ignore
private final HashMap<String, String> internalMap = new HashMap<>();
/* concerning the second part of the question: */
public HashMap<String, Object> toHashMap() {
return this.internalMap;
}
}
besides those getters have the fundamental problem, that they assume all the keys would exist, despite the HashMap might not have been populated with all (or any) of those keys. and it's absurd to represent a city with two different types of object - while one has full control over which fields are being mapped and which are being ignored (one possible approach does not have to exclude the other here)... a method to return the HashMap might be useful, eg. in order to insert that into Firebase.
I'd rather go for fields holding the values, while nevertheless being able to return a HashMap:
#Entity(tableName = "cities")
public class City {
#ColumnInfo(name = "cityId")
#PrimaryKey(autoGenerate = true)
private int cityId = -1;
#ColumnInfo(name = "cityName")
private String cityName = null;
...
/* concerning the second part of the question: */
public HashMap<String, Object> toHashMap() {
HashMap<String, Object> values = new HashMap<>();
if(this.cityId != -1) {values.put( "id", this.cityId);}
if(this.cityName != null) {values.put("name", this.cityName);}
return values;
}
}
such a toHashMap() method also provides control, which fields the returned HashMap shall contain, while the example with the TypeConverter & GSON would (as it is) convert the whole HashMap.

Related

DC2type on greenDao

I am using GreenDao for Android application, with some specification, for example, I have a Contact Model with some information like name, avatar, phone number, etc...
Right now the need is to change from only one phone number to a multiphone number.
Instead of creating two tables (table for numbers, and table for contacts), I really need just one information is the number so in my backend the contact numbers is stocked on a DC2type, (a json array saved as a string).
Do we have a possibility to do that using GreenDao?
i search for a solution or a DC2type implementation , etc ... and nothing is found
so i decide to created by my self , and this is what i did :
using the #Convert annotation presented of GreenDao 3 :
#Property(nameInDb = "phoneNumbers")
#Convert(converter = PhoneNumbersConverter.class, columnType = String.class)
private List<String> phoneNumbers;
static class PhoneNumbersConverter implements PropertyConverter<List<String>, String> {
#Override
public List<String> convertToEntityProperty(String databaseValue) {
List<String> listOfStrings = new Gson().fromJson(databaseValue,List.class);
return listOfStrings;
}
#Override
public String convertToDatabaseValue(List<String> entityProperty) {
String json = new Gson().toJson(entityProperty);
return json;
}
}
short story long , i create a json to array parser
thanks to myself to helped me :D

How to store objects in Android Room?

Basically, there are two things I don't understand: objects with objects and objects with lists of objects
Say I receive a list of objects from the server. Each of them looks like this:
#Entity
public class BigObject {
#PrimaryKey
private int id;
private User user;
private List<SmallObject> smallObjects;
}
with these two objects as fields:
#Entity
public class User {
#PrimaryKey
private int id;
private String name;
#TypeConverters(GenderConverter.class)
public MyEnums.Gender gender;
}
#Entity
public class SmallObject {
#PrimaryKey (autoGenerate = true)
private int id;
private String smallValue;
}
They are more complicated than this, so I can't use #TypeConverters as Room suggests:
error: Cannot figure out how to save this field into database. You can consider adding a type converter for it.
How do I store this data structure in Room?
I think the best way to answer this is a breif overview in storing structures...
Lists
Room does not support storing lists that are nested inside of a POJO. The recommended way to store lists is to use the foreign key approach. Store the List of objects in a seperate table (in this case a smallObjects table) with a foreign key to their related parent object (in this case "big_object_id"). It should look something like this...
#Entity
public class BigObject {
#PrimaryKey
private int id;
private User user;
#Ignore
private List<SmallObject> smallObjects;
}
#Entity(foreignKeys = {
#ForeignKey(
entity = BigObject.class,
parentColumns = "id",
childColumns = "big_object_fk"
)})
public class SmallObject {
#PrimaryKey (autoGenerate = true)
private int id;
private String smallValue;
#ColumnInfo(name = "big_object_fk")
private int bigObjectIdFk
}
Note that we have added the #Ignore annotaiton to List<SmallObject> as we want to ignore the field during Room persistance (as lists are not supported). It now exists so that when we request our list of related small objects from the DB we can still store them in the POJO.
To my knowledge this will mean you are making two queries.
BigObject b = db.BigObjectDao.findById(bOId);
List<SmallObject> s = db.smallObjectDao.findAllSOforBO(bOId);
b.setsmallObjects(s);
It appears that there is a short hand for this in the form of #Relation
Type Converters
These are for cases where you have a complex data structure that can be flattend without losing information, and stored in a single column. A good example of this is the Date object. A Date object is complex and holds a lot of values, so storing it in the database is tricky. We use a type converter to extract the milli representation of a date object and store that. We then convert the millis to a date object on the way out thus keeping our data intact.
Embedded
This is used when you want to take the fields of all nested POJOs in your parent POJO and flatten them out to store in one table. an example :
- name
- age
- location
- x
- y
- DOB
..when embedded this structure would be stored in the database as :
- name
- age
- location_x
- location_y
- DOB
In a sense Embedded exists to save you time creating type converters for every nested object that contains primary type fields like String, int, float, etc...
Convert Object/List<Object> to String and then, Store it.
You can store the objects in Room Library as String. For that, you can serialize the object and store it as String in the Room Database.
Store to Room
Object -> Serialize -> String -> Store
Read from Room
String -> Deserialize ->Object -> Read
How to Serialize/Deserialize?
There are many options available. You can either do it manually or you can use a library for this. You can use Google's GSON library. It is pretty easy to use.
Code: Object -> String
public String stringFromObject(List<YourClass> list){
Gson gson = new Gson();
String jsonString = gson.toJson(list);
return jsonString;
}
Code: String-> Object
public List<YourClass> getObjectFromString(String jsonString){
Type listType = new TypeToken<ArrayList<YourClass>>(){}.getType();
List<YourClass> list = new Gson().fromJson(jsonString, listType);
return list;
}

How to manage firebase database keys?

When using firebase (or any database that aggregates data basing on ids) I nearly always have to keep track of a key of a given value. For example let's assume I have Location class with latitude and longitude fields. When I download if from firebase, besides its two fields, I also want to keep track of the key (node value generated with push() e.g. -K_esEYXNfMBNmgF3fO4) it was downloaded from so I may later update it, delete it etc. I see only two solutions:
Duplicate the data and add key value as another Location class field. That doesn't work nicely because I have to set the key value only after I executed push().
Create generic wrapper class that will keep key and object:
public class Key<T> {
private final String key;
private final T value;
public Key(String key, T value) {
this.value = value;
this.key = key;
}
public String key() {
return key;
}
public T value() {
return value;
}
}
I am using the second approach but it doesn't look really nice. I have this Key class basically throughout all my codebase and when using RxJava plenty of methods have a return type like this: Observable<Key<Location>> and that just looks ridiculous.
What you call ridiculous actually looks quite normal to me.
Alternatively you can include the key in the POJO and annotate it with #Exclude to exclude it from the serialization.
Follow up on #FrankvanPuffelen great answer, do what you want with the below pushkey
Read and Write Data on Android
private void writeNewPost(String userId, String username, String title, String body) {
// Create new post at /user-posts/$userid/$postid and at
// /posts/$postid simultaneously
String key = mDatabase.child("posts").push().getKey();
Post post = new Post(userId, username, title, body);
Map<String, Object> postValues = post.toMap();
Map<String, Object> childUpdates = new HashMap<>();
childUpdates.put("/posts/" + key, postValues);
childUpdates.put("/user-posts/" + userId + "/" + key, postValues);
mDatabase.updateChildren(childUpdates);
}

Firebase data insert issue. Data is not inserted properly

I am trying to insert a POJO to Firebase. However, some of the fields don't seem to be parsed into Firebase, but there is no warning or error.
I have this POJO:
public class Group {
public String name;
public String admin;
public List<String> addedUsers;
public List<String> invitedUsers;
public Group(String name, String admin, ArrayList<String> addedUsers, ArrayList<String> invitedUsers) {
this.name = name;
this.admin = admin;
this.addedUsers = addedUsers;
this.invitedUsers = invitedUsers;
}
public Group() {
// Default constructor required because we have a non-default constructor as well.
}
}
I upload to Firebase by doing so:
DatabaseReference groupRef = ref.child("Groups");
ArrayList<String> addedUsers = new ArrayList<String>();
addedUsers.add("email1#gmail.com");
addedUsers.add("email2#gmail.com");
ArrayList<String> invitedUsers = new ArrayList<String>();
Group newGroup = new Group("GroupName",
"email1#gmail.com", addedUsers, invitedUsers
);
groupRef.push().setValue(newGroup);
I end up with this object in Firebase:
I have a secondary issue now, I manually inserted the data into Firebase, but now I cannot map the Lists onto my Java Object, and are mapped as null, I know I am able to download the data fine;
I'm not sure what you mean that lists are not supported, as it seems that they are supported.
Basic write operations
For basic write operations, you can use setValue() to save data to a
specified reference, replacing any existing data at that path. You can
use this method to:
Pass types that correspond to the available JSON types as follows:
String
Long
Double
Boolean
Map<String, Object>
List<Object>
Pass a custom Java object, if the class that defines it has a default
constructor that takes no arguments and has public getters for the
properties to be assigned.
Firebase supports key value mapping. So lists are not supported. Change it to Map type, keep email addresses as key and assign a boolean value true or false.

Persisting array of strings with greenDao

I'm trying to map an object to database with greenDao. But when it comes to arrays, I don't know how to do it. After receiving JSON from network and deserializing it with GSON, I have objects defined by this class:
public class Car {
Long carId;
String name;
ArrayList<String> listOfLinks;
}
In case of a a different architecture, like this:
public class Car {
Long carId;
String name;
ArrayList<Link> listOfLinks;
}
public class Link {
Long carId;
String link;
}
----
Entity cars = schema.addEntity("Car");
cars.addLongProperty("carId").primaryKey();
cars.addStringProperty("name");
Entity links = schema.addEntity("Link");
links.addStringProperty("name");
links.addIdProperty().primaryKey().notNull().autoincrement();
Property linkProperty = links.addLongProperty("carId").getProperty();
ToMany carToLinks = cars.addToMany(link, linkProperty);
It would is easy. Define some relations, define properties, add foreign key and your done. With arrays I have no clue what to do. Ideas?
That approach is not common when using relational databases.
This is commonly done using to-many relations : instead of using a list of String, you can create a Link entity and then use a list of Link.
Relation toMany is useful when you have a list of your not primitive object, that you can declare like entity that have its own id etc etc etc, and make list of entities (with toMeny). By doing that greenDao makes another table in the base for you new entity with the foreign key of the base entity that contains list. When you have list of primitive type the only way to do is to make converter that converts List into one of the primitive types that greenDao works naturally. You have to do something like this `
import org.greenrobot.greendao.converter.PropertyConverter;
import java.util.Arrays;
import java.util.List;
/**
*DOLE BREEE SQLITE BREEEEEE!!!**
*i choosed to convert List into one string
*that is going to be saved in database, and vice versa
*/
public class GreenConverter implements PropertyConverter, String> {
#Override
public List convertToEntityProperty(String databaseValue) {
if (databaseValue == null) {
return null;
}
else {
List<String> lista = Arrays.asList(databaseValue.split(","));
return lista;
}
}
#Override
public String convertToDatabaseValue(List<String> entityProperty) {
if(entityProperty==null){
return null;
}
else{
StringBuilder sb= new StringBuilder();
for(String link:entityProperty){
sb.append(link);
sb.append(",");
}
return sb.toString();
}
}
}
now above all the properties that are List you have to put
#Convert(converter=yourconverterclass.class, columnType = String.class)
#Entity
public class ShipEntry {
#Id(autoincrement = true)
private long ship_id;
private String name;
private String model;
private String manufacturer;
private String starship_class;
#Convert(converter = GreenConverter.class, columnType = String.class)
private List<String> pilots;
#Convert(converter = GreenConverter.class, columnType = String.class)
private List<String> films ;
}
you can create Converter as a inner class of entitiy, and in that case it has to be declared as staticthat is the only way i have found, but the bad side is that you can not use property that you are converting into query. There might me some typo, but i hope this helps to solve your problem
I also have the same issue, and there no answer (not in official docs, not in google). Please explain how to map List to Entity?
public class Car {
Long carId;
String name;
ArrayList<String> listOfLinks;
}
Can I do something like this?
#Entity(active = true, nameInDb = "CARS")
public class Car {
#Id
private Long id;
#NotNull
#Unique
private String remoteId;
#ToMany(joinProperties = {
#JoinProperty(name = "remoteId", referencedName = "carRemoteId")
})
private List<Links> listOfLinks;
}
#Entity(active = true, nameInDb = "LISTOFLINKS")
public class Links{
#Id
private Long id;
#NotNull
#Unique
private String remoteId;
#SerializedName("listOfLinks")
#Expose
private String listOfLinks;//is it possible?????
private String carRemoteId;
}
Since JPA 2.0, you can use an element collection to persist a Collection of value types. You just need to annotate the attribute with #ElementCollection and the persistence provider will persist the elements of the Collection in an additional database table.
#Entity
public class Author {
#ElementCollection
private List<String> phoneNumbers = new ArrayList<String>();
}
The element collection might seem easier to use than an entity with a one-to-many association. But it has one major drawback: The elements of the collection have no id and Hibernate can’t address them individually.
When you add a new Object to the List or remove an existing one, Hibernate deletes all elements and inserts a new record for each item in the List.
Let’s take a quick look at an example. The following code snippet selects an Author entity and adds a second phoneNumber to the element collection.
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
Author a = em.find(Author.class, 1L);
a.getPhoneNumbers().add("42424242");
em.getTransaction().commit();
em.close();
an element collection is an easy but not the most efficient option to store a list of value types in the database. You should, therefore, only use it for very small collections so that Hibernate doesn’t perform too many SQL statements. In all other cases, a one-to-many association is the better approach.

Categories

Resources