I'm new to Firebase, and I've been really enjoying it so far. I'm running into a problem; I'm using the FirebaseListAdapter similar to the tutorial outline here: https://github.com/firebase/AndroidChat
To use the FirebaseListAdapter, I need to use data model objects (to get the automatic binding to work nicely). The problem is I also want to keep a timestamp value with that model object, and I want to get the timestamp from the Firebase server.
What I have currently that is NOT working is a class DataModelObject (similar to com.firebase.androidchat.Chat in the demo example) with a constructor like :
DataModelObject(String data1, String data2, Map enQTimeStamp)
which I then try to use like this:
DataModelObject dmo = new DataModelObject ("foo", "bar", ServerValue.TIMESTAMP);
myFirebaseRef.push().setValue(dmo);
This causes a JsonMappingException when I try to run that code. I found a code snippet here :
https://www.firebase.com/blog/2015-02-11-firebase-unique-identifiers.html
But it's worthwhile to note that on line 4 of the Android code example, that will cause a compile time error (as he is trying to put ServerValue.TIMESTAMP into a Map, and TIMESTAMP is a Map itself)
What is the right way to do this and maintain compatibility with FirebaseListAdapter?
This sounds similar to this question: When making a POJO in Firebase, can you use ServerValue.TIMESTAMP?
When creating POJOs used to store/retrieve data apart from the default empty constructor I usually use a constructor similar to this:
Param param1;
Param param2;
HashMap<String, Object> timestampCreated;
//required empty constructor
public DataObject(){}
public DataObject(Param param1, Param param2) {
this.param1 = param1;
this.param2 = param2;
HashMap<String, Object> timestampNow = new HashMap<>();
timestampNow.put("timestamp", ServerValue.TIMESTAMP);
this.timestampCreated = timestampNow;
}
Be sure to include a getter for the HashMap<> used to store the Timestamp:
public HashMap<String, Object> getTimestampCreated(){
return timestampCreated;
}
Then use the #Exclude annotation to create a getter that you can use in your code to get the value of the timestamp if you need it. The #Exclude annotation will cause Firebase to ignore this getter and not look for a corresponding property
#Exclude
public long getTimestampCreatedLong(){
return (long)timestampCreated.get("timestamp");
}
Here's how I do it
//member variable
Object createdTimestamp;
public YourConstructor(){
createdTimestamp = ServerValue.TIMESTAMP
}
#Exclude
public long getCreatedTimestampLong(){
return (long)createdTimestamp;
}
Your db object should include these:
public class FirebaseDbObject {
private final Object timestamp = ServerValue.TIMESTAMP;
//........
//........
Object getTimestamp() {
return timestamp;
}
#Exclude
public long timestamp() {
return (long) timestamp;
}
}
This will add an extra field called "timestamp" to your object.
Edit: The answer posted by MobileMon is not fully correct as it does not have getter method. This is the complete and correct answer.
Kotlin provides an easy way to achieve this by data classes. You can create it like
data class FirebaseRequestModel(
var start_time: Any = ServerValue.TIMESTAMP,
var stop_time: Long = 0,
var total_time: Long = 0,
)
and use it directly by
val firebaseModel = FirebaseRequestModel()
firebaseRef.setValue(firebaseModel)
This will get default values from data class.
Or even you can initiate your own values by
val firebaseModel = FirebaseRequestModel(ServerValue.TIMESTAMP, 2134, 0)
firebaseRef.setValue(firebaseModel)
Similar to Urgurcan's answer, but a bit cleaner so the caller doesn't have trouble guessing between getTimestamp vs timestamp.
public class FirebaseDbObject {
private Object timestamp = ServerValue.TIMESTAMP;
//........
//........
#PropertyName("timestamp")
Object getRawTimestamp() {
return timestamp;
}
#Exclude
public long getTimestamp() {
return (long) timestamp;
}
}
You can do it:
public class MyTimeStamp {
private Object timestamp;
public MyTimeStamp() {
}
public Object getTimestamp() {
return timestamp;
}
public void setTimestamp(Object timestamp) {
this.timestamp = timestamp;
}
}
And so:
public static void start(Context context) {
MyTimeStamp timeStamp = new MyTimeStamp();
timeStamp.setTimestamp(ServerValue.TIMESTAMP);
Log.d(TAG, "start: ", timeStamp.getTimestamp().toString());
}
Related
My android application is crashing on start up, because of a small error in my back-end side which is in firebase. A document in my firestore database has a field in which an Image object (id and url) is expected but for some reason it's stored as a long in the database.
I get this error:
java.lang.RuntimeException: Could not deserialize object. Can't convert object of type java.lang.Long to type com..data.entity.gps.tracker.ImageEntity
My ImageEntity class:
public class ImageEntity {
private int id;
private String image_id;
public String getImage_id() {
return image_id;
}
public void setImage_id(String image_id) {
this.image_id = image_id;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
I can't just exclude the cover ImageEntity object, it's an integral part of my app, how can I add a validation step to check if the cover object is a valid object and not a long?
This is where the crash happens:
public List<Model> mapList(QuerySnapshot querySnapshot) {
List<Model> list = new ArrayList<>();
for (QueryDocumentSnapshot queryDocumentSnapshot : querySnapshot) {
Model model = map(queryDocumentSnapshot);
long id = ((Release) model).getId();
if (!addedReleasesIDs.contains(id)) {
list.add(model);
addedReleasesIDs.add(id);
}
}
return list;
}
public Model map(QueryDocumentSnapshot queryDocumentSnapshot) {
// Auto serialization
Entity entity = queryDocumentSnapshot.toObject(getEntityClass()); // CRASH HERE
return map(entity);
}
If you need to take control over the deserializaition of a document snapshot, you should access its fields as a Map<String, Object> using getData() instead of using the Firebase SDKs internal mapper.
Map<String, Object> data = (Map<String, Object>) queryDocumentSnapshot.getData();
With this, you can now get() the individual fields by name out of the data map, check their types, and convert them to whatever other type you want for your model object.
This is also faster in terms of performance than using toObject because it doesn't have to use Java reflection.
I am pretty weak with JSON, and probably have a silly question, and was wondering how to parse a JSON object placed inside a JSON array.
So, as of now, I have
public Single<Profile> doProfileApiCall() {
return Rx2AndroidNetworking.post(ApiEndPoint.ENDPOINT_PROFILE)
.addHeaders(mApiHeader.getProtectedApiHeader())
.build()
.getObjectSingle(Profile.class);
To retrieve my profile params, but in my endpoints I have :
[{"Name": "this", "Email","that#gmail.com"}]
I have my endpoint set up as :
public static final String ENDPOINT_PROFILE =
BuildConfig.BASE_URL
+ "/linktojson";
which gives me the above JSON.
but the issue is the [], how do I modify this with :
public Single<Profile> doProfileApiCall() {
return Rx2AndroidNetworking.post(ApiEndPoint.ENDPOINT_PROFILE)
.addHeaders(mApiHeader.getProtectedApiHeader())
.build()
.getObjectSingle(Profile.class);
such that I can use my profile.java model class which has
public class Profile {
#Expose
#SerializedName("Name")
private String name;
#Expose
#SerializedName("Email")
private String email;
etc...
}
Any idea how to go about this?
In the doProfileApiCall() method instead of .getObjectSingle use
.getJSONArraySingle(ProfileList.class)
Now create a new class ProfileList.java with the following code.
List<Profile> profileList = new ArrayList<>();
public List<Profile> getProfileList() {
return profileList;
}
public void setProfileList(List<Profile> profileList) {
this.profileList = profileList;
}
Change the returntype of the doProfileApiCall method to
public Single<ProfileList> doProfileApiCall()
Whenever you want to access the data use it with the list position 0, when in future you get more data, you can index the data accordingly.
Generally, if JSON root object is an array you should use List on Java side. In your case you have array so use related method:
return Rx2AndroidNetworking.post(ApiEndPoint.ENDPOINT_PROFILE)
.addHeaders(mApiHeader.getProtectedApiHeader())
.build()
.getObjectListSingle(Profile.class);
Rx2ANRequest source.
I wonder to know how Firebase serialize/deserialize POJO object to/from json, does it use Jackson or Gson or any similar library else.
I have trouble about naming convention with Firebase. My model some like this:
class Data {
private String someFieldName;
private String anotherFieldName;
public Data() {}
public void setSomeFieldName(String) {...}
public String getSomeFieldName(String) {...}
public void setAnotherFieldName(String) {...}
public String getAnotherFieldName() {...}
}
And the expected result in Firebase should be:
{
"some_field_name" : "...",
"another_field_name" : "..."
}
with Gson I can use FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES for my purpose, as in Gson doc:
Here's a few examples of the form "Java Field Name" ---> "JSON Field Name":
someFieldName ---> some_field_name
_someFieldName ---> _some_field_name
aStringField ---> a_string_field
aURL ---> a_u_r_l
How can I convert my POJO object to "Firebase value" with specific naming convention and vice versa, or there are any way to customize the serialize/deserialize process?
Thanks!
When reading the data back from the Firebase database you can use the #PropertyName annotation to mark a field to be renamed when being serialized/deserialized, like so:
#IgnoreExtraProperties
class Data {
#PropertyName("some_field_name")
public String someFieldName
#PropertyName("another_field_name")
private String anotherFieldName;
public Data() {}
}
Make sure that your field is public and not private or else the annotation will not work (I also believe that Firebase uses Jackson to handle the object mapping under the hood, but don't think you can actually customize HOW it uses it).
Personally I prefer keeping explicit control over the serialization/deserialization process, and not relying on specific framework and/or annotations.
Your Data class can be simply modified like this :
class Data {
private String someFieldName;
private String anotherFieldName;
public Data() {}
public Data(Map<String, Object> map) {
someFieldName = (String) map.get("some_field_name") ;
anotherFieldName = (String) map.get("another_field_name") ;
}
public Map<String, Object> toMap() {
Map<String, Object> map = new HashMap<>();
map.put("some_field_name", someFieldName);
map.put("another_field_name", anotherFieldName);
return map ;
}
}
For writing value to firebase, simply do :
dbref.setValue(data.toMap());
For reading :
Map<String, Object> map = (Map<String, Object>) dataSnapshot.getValue();
data = new Data(map);
They are some advantages with this solution :
No assumption is made on underlying json framework
No need to use annotations
You can even further decouple you Object model from your Json model by externalizing the methods toMap() and constructor to a DataMapper (snippet hereunder)
public static Data fromMap(Map<String, Object> map) {
String someFieldName = (String) map.get("some_field_name") ;
String anotherFieldName = (String) map.get("another_field_name") ;
return new Data(someFieldName, anotherFieldName);
}
public static Map<String, Object> toMap(Data data) {
Map<String, Object> map = new HashMap<>();
map.put("some_field_name", data.getSomeFieldName());
map.put("another_field_name", data.getAnotherFieldName());
return map ;
}
i have a problem getting timestamp(rowversion) from my SQL Azure database.
In my tables there is a column with datatype timestamp. This timestamp isn't similar to datetime, it's more like a rowversion.
I can get all other data in this table with the query from MobileServiceTable, there is no problem.
But this special datatype is a problem.
My class for this table looks like:
public class ArbeitsgangBezeichnung {
#com.google.gson.annotations.SerializedName("id")
private int ID;
#com.google.gson.annotations.SerializedName("ABZ_ArbeitsgangBezeichnungID")
private int ABZ_ArbeitsgangBezeichnungID;
#com.google.gson.annotations.SerializedName("ABZ_Bezeichnung")
private String ABZ_Bezeichnung;
#com.google.gson.annotations.SerializedName("ABZ_RowVersion")
private StringMap<Number> ABZ_RowVersion;
//constructor, getter, setter, etc....
}
If i login in Azure and look at the table, there are my example values and the automatic generated timestamp. The timestamp value looks like "AAAAAAAAB/M=". If i login in sql database and let me show the data, then for timestamp there is only "binarydata" (in pointed brackets) and not that value as it is shown in Azure.
The variable "ABZ_RowVersion" should include this timestamp, but the data in the StringMap doesn't look like the one in Azure. I tried String and Byte as datatype for the StringMap, but it doesn't helped.
I tried byte[] for ABZ_RowVersion, but then i got an exception in the callback method.
Then i tried Object for ABZ_RowVersion, that time i found out, that it is a StringMap, but nothing more.
Does anybody know, how to get the data from timestamp, i need it for comparison.
Thanks already
When you create a timestamp column in a table, it's essentially a varbinary(8) column. In the node SQL driver, it's mapped to a Buffer type (the usual node.js type used for binary data). The object which you see ({"0":0, "1":0, ..., "length":8}) is the way that a buffer is stringified into JSON. That representation doesn't map to the default byte array representation from the Gson serializer in Android (or to the byte[] in the managed code).
To be able to use timestamp columns, the first thing you need to do is to "teach" the serializer how to understand the format of the column returned by the server. You can do that with a JsonDeserializer<byte[]> class:
public class ByteArrayFromNodeBufferGsonSerializer
implements JsonDeserializer<byte[]> {
#Override
public byte[] deserialize(JsonElement element, Type type,
JsonDeserializationContext context) throws JsonParseException {
if (element == null || element.isJsonNull()) {
return null;
} else {
JsonObject jo = element.getAsJsonObject();
int len = jo.get("length").getAsInt();
byte[] result = new byte[len];
for (int i = 0; i < len; i++) {
String key = Integer.toString(i);
result[i] = jo.get(key).getAsByte();
}
return result;
}
}
}
Now you should be able to read data. There's still another problem, though. On insert and update operations, the value of the column is sent by the client, and SQL doesn't let you set them in them. So let's take this class:
public class Test {
#SerializedName("id")
private int mId;
#SerializedName("name")
private String mName;
#SerializedName("version")
private byte[] mVersion;
public int getId() { return mId; }
public void setId(int id) { this.mId = id; }
public String getName() { return mName; }
public void setName(String name) { this.mName = name; }
public byte[] getVersion() { return mVersion; }
public void setVersion(byte[] version) { this.mVersion = version; }
}
On the insert and update operations, the first thing we need to do in the server-side script is to remove that property from the object. And there's another issue: after the insert is done, the runtime doesn't return the rowversion property (i.e., it doesn't update the item variable. So we need to perform a lookup against the DB to retrieve that column as well:
function insert(item, user, request) {
delete item.version;
request.execute({
success: function() {
tables.current.lookup(item.id, {
success: function(inserted) {
request.respond(201, inserted);
}
});
}
});
}
And the same on update:
function update(item, user, request) {
delete item.version;
request.execute({
success: function() {
tables.current.lookup(item.id, {
success: function(updated) {
request.respond(200, updated);
}
});
}
});
}
Now, this definitely is a lot of work - the support for this type of column should be better. I've created a feature request in the UserVoice page at http://mobileservices.uservoice.com/forums/182281-feature-requests/suggestions/4670504-better-support-for-timestamp-columns, so feel free to vote it up to help the team prioritize it.
I use ormlite and I have a db with a field:
public static final String NAME = "name";
#DatabaseField (canBeNull = false, dataType = DataType.SERIALIZABLE, columnName = NAME)
private String[] name = new String[2];
And I would like to get all elements that name[0] and name[1] are "car". I try to add a where clausule like:
NAMEDB nameDB = null;
Dao<NAMEDB, Integer> daoName = this.getHelper().getDao(NAMEDB.class);
QueryBuilder<NAMEDB, Integer> queryName = daoName.queryBuilder();
Where<NAMEDB, Integer> where = queryName.where();
where.in(nameDb.NAME, "car");
But it doesn't work because it's an array string.
I have other fields:
public static final String MARK = "mark";
#DatabaseField (canBeNull = false, foreign = true, index = true, columnName = MARK)
private String mark = null;
And I can do this:
whereArticulo.in(nameDB.MARK, "aaa");
How can I solve my problem? Thanks.
It seems to me that a third option to store a string array (String[] someStringArray[]) in the database using Ormlite would be to define a data persister class that converts the string array to a single delimited string upon storage into the database and back again to a string array after taking it out of the database.
E.g., persister class would convert ["John Doe", "Joe Smith"] to "John Doe | Joe Smith" for database storage (using whatever delimiter character makes sense for your data) and converts back the other way when taking the data out of the database.
Any thoughts on this approach versus using Serializable or a foreign collection? Anyone tried this?
I just wrote my first persister class and it was pretty easy. I haven't been able to identify through web search or StackOverflow search that anyone has tried this.
Thanks.
As ronbo4610 suggested, it is a good idea to use a custom data persister in this case, to store the array as a string in the database separated by some kind of delimiter. You can then search the string in your WHERE clause just as you would any other string. (For example, using the LIKE operator)
I have implemented such a data persister. In order to use it, you must add the following annotation above your String[] object in your persisted class:
#DatabaseField(persisterClass = ArrayPersister.class)
In addition, you must create a new class called "ArrayPersister" with the following code:
import com.j256.ormlite.field.FieldType;
import com.j256.ormlite.field.SqlType;
import com.j256.ormlite.field.types.StringType;
import org.apache.commons.lang3.StringUtils;
public class ArrayPersister extends StringType {
private static final String delimiter = ",";
private static final ArrayPersister singleTon = new ArrayPersister();
private ArrayPersister() {
super(SqlType.STRING, new Class<?>[]{ String[].class });
}
public static ArrayPersister getSingleton() {
return singleTon;
}
#Override
public Object javaToSqlArg(FieldType fieldType, Object javaObject) {
String[] array = (String[]) javaObject;
if (array == null) {
return null;
}
else {
return StringUtils.join(array, delimiter);
}
}
#Override
public Object sqlArgToJava(FieldType fieldType, Object sqlArg, int columnPos) {
String string = (String)sqlArg;
if (string == null) {
return null;
}
else {
return string.split(delimiter);
}
}
}
Unfortunately ORMLite does not support querying fields that are the type SERIALIZABLE. It is storing the array as a serialized byte[] so you cannot query against the values with an IN query like:
where.in(nameDb.NAME, "car");
ORMLite does support foreign collections but you have to set it up yourself with another class holding the names. See the documentation with sample code:
http://ormlite.com/docs/foreign-collection