Android Room Base64 - java

I am trying to save a base64 value into my sqlite database using Room and for some reason it's not saving. Well, i'm assuming it's not saving because when I try to read the table that has the base64 column, it returns values for all the other columns except the base64 column. What am I doing wrong?
My Entity:
#Entity(tableName = "healthCareWorkerInformation",
foreignKeys = #ForeignKey(
entity = HealthCareWorker.class,
parentColumns = {"id"},
childColumns = {"hcwId"},
onDelete = ForeignKey.CASCADE),
indices = #Index(
value = {"hcwId"}))
public class HealthCareWorkersInformation {
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "hcwInfoId")
private long id;
private long hcwId;
//#ColumnInfo(typeAffinity = ColumnInfo.BLOB)
private String base64Image;
private String updatedAt = new SimpleDateFormat("dd MM yyyy, HH:mm",
Locale.getDefault()).format(Calendar.getInstance().getTime());
public HealthCareWorkersInformation() {
}
#Ignore
public HealthCareWorkersInformation(long hcwId) {
this.hcwId = hcwId;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public long getHcwId() {
return hcwId;
}
public void setHcwId(long hcwId) {
this.hcwId = hcwId;
}
public String getBase64Image() {
return base64Image;
}
public void setBase64Image(String base64Image) {
this.base64Image = base64Image;
}
public String getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(String updatedAt) {
this.updatedAt = updatedAt;
}
}
My DAO:
#Insert
void insertHealthCareWorkerInformation(HealthCareWorkersInformation healthCareWorkersInformation);
#Query("SELECT * FROM HEALTHCAREWORKERINFORMATION")
LiveData<List<HealthCareWorkerInformation>> getHCWInfo();
Sample data I send through:
{"consentGiven":null,"hcwId":1,"patiendId":1,"name":"Ben","lastName":"Ben","dateOfBirth":"4/9/2019","phoneNumber":"+271234567","base64Image":"data:image/png;base64,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"}
Everything else gets saved except for the base64 column. Please assist.

The problem is the semicolon in data:image/png;base64, in combination with " double quotes.
The most easy would be to save the base64 string without that prefix, which breaks the syntax and then assume it is all PNG images (or add a further field, which indicates the encoding of each image).
This problem isn't specific to Room, but specific to Java with SQLite, because that semicolon terminates the statement (which Room will generate). In Java, one can use ' single-quotes only for the primitive data-type char, while the complex data-type String excepts " double-quotes. The only way to get around this limitation, is not trying to save a String containing a ;.
To provide an example of what I mean:
private String base64String = null;
private String base64Type = "png";
public String getBase64Image() {
if(this.base64String != null) {
return "data:image/" + this.base64Type + ";base64," + this.base64String;
} else {
return null;
}
}

Related

How to deserialize a String to a Date with Morphia

I have a Mongo collection with objects of this format:
{
id: 1,
date: "2020-08-06T12:00:00Z",
...
}
I have Java code that needs to read from this collection but never writes to it. The process that writes to this collection is not owned by me so I can't necessarily change the format of that date string. I initially tried to model my Java Morphia object like this:
#Entity public class MyDocument {
#Id
private Integer id;
private Date date;
...
}
This did not work because Morphia didn't know how to deserialize that date format into a Date object. The solution that I came up with was treating the date as a String on the POJO and then having a getDate() method that did the actual deserialization. I am wondering, is there a better way for me to do this? I know if you're using Jackson you can annotate certain fields with #JsonDeserialize and pass a deserializer so I was wondering if there was something similar for Morphia.
My solution (which feels suboptimal to me):
#Entity public class MyDocument {
#Id
private Integer id;
private String date;
...
private Date getDate() {
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
try {
return dateFormat.parse(date);
} catch (Exception ex) {
return null;
}
}
}
You can go ahead and create a simple converter extending the TypeConverter like so:
public class DateConverter extends TypeConverter {
private static final String FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
private final SimpleDateFormat simpleDateFormat;
public DateConverter() {
super(Date.class);
this.simpleDateFormat = new SimpleDateFormat(FORMAT);
}
#Override
public Object decode(Class<?> targetClass, Object fromDBObject, MappedField optionalExtraInfo) {
try {
return simpleDateFormat.parse(((String) fromDBObject));
} catch (ParseException e) {
return null;
}
}
}
The go ahead and register your formatter for your document entity like so:
#Entity("Documents")
#Converters(DateConverter.class)
public class Document {
#Id
private Integer id;
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
private Date date;
public Date getDate() { return date; }
public void setDate(Date date) { this.date = date; }
#Override
public String toString() {
return "Document{" +
"id=" + id +
", date=" + date +
'}';
}
}
This will effectively tell Morphia to decode the database incoming values via parsing the string with the desired pattern, resulting directly into a concrete Date object without any additional conversion logic.

How to migrate a Room database adding a Date column?

I have a simple database, with one table (id, title and description), I want to add a column to storage a duedate. I use the info I found in Developer android migration and TypeConverters to migrate it, I can install to test, but it doesn't open the app.
I'll apreciate any help!
My entity
#Entity(tableName = "todo_db")
public class todoEnt {
#PrimaryKey(autoGenerate = true)
public int id;
#ColumnInfo(name = "title")
public String todoTitle;
#ColumnInfo(name = "description")
public String todoDescription;
#ColumnInfo(name = "dueDate")
public Date dueDate;
}
I have a Typeconverter
public class Convertors {
#TypeConverter
public static Date fromTimeStamp(Long value){
return value == null ? null : new Date(value);
}
#TypeConverter
public static Long dateToTimestamp(Date date){
if (date == null) {
return null;
} else {
return date.getTime();
}
}
}
And I added this to my database file
#TypeConverters({Convertors.class})
static final Migration M_1_2 = new Migration(1, 2) {
#Override
public void migrate(#NonNull SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE todo_db ADD duedate INTEGER");
}
};
INSTANCE = Room.databaseBuilder(
context.getApplicationContext(),
TDAHdb.class,
"tdah_database")
.addMigrations(M_1_2)
.allowMainThreadQueries()
.build();
With Room migrations, table and column names are case-sensitive. Change duedate in your migration to match the case of the #ColumnInfo name specified in the entity definition, dueDate:
database.execSQL("ALTER TABLE todo_db ADD dueDate INTEGER");

Alternative to multiple constructors

I have this constructor...
public ShiftLog(String companyName, boolean workedForAgent, String agentName,
Date shiftStart, Date shiftEnd,
boolean breakTaken, Date breakStart,
Date breakEnd, boolean isTransportJob,
String transportCompanyName, String vehicleRegistration) {
this.userUid = FirebaseAuth.getInstance().getCurrentUser().getUid();
this.companyName = companyName;
this.workedForAgent = workedForAgent;
this.agentName = agentName;
this.shiftStart = shiftStart;
this.shiftEnd = shiftEnd;
this.breakTaken = breakTaken;
this.breakStart = breakStart;
this.breakEnd = breakEnd;
this.isTransportJob = isTransportJob;
this.transportCompanyName = transportCompanyName;
this.vehicleRegistration = vehicleRegistration;
}
Now I want to add in a shift log (instantiate a shift log object for a user). The problem is that there are multiple combinations a shift log can have. For example, workedForAgent is false, there should be no need to pass in agentName. How can I do that without creating multiple constructors because there can be multiple possible combinations? For example, user can work for agent but not take a break, meaning break start time and end time shouldn't be needed to pass in. But that would require so many constructors for all possible combinations. Any alternative?
Also I am using the room database to append all this info. So if workedForAgent is false for example, automatically set agentName to null. How could that be done as well.
Take a look at Builder patterns.
Builder pattern is a creational design pattern it means its solves problem related to object creation.
It typically solve problem in object oriented programming i.e determining what constructor to use.
Adding to #Kodiak
You can replace your constructor with builder in few clicks
as mentioned here https://www.jetbrains.com/help/idea/replace-constructor-with-builder.html
Plus, the best part is,it will refactor all the occurrence of the constructor with builder automatically
Short Answer: Use Getters/Setters
Long Answer: The alternative method here is that you can instantiate the variables that you sure they must exist in the constructor and then the other conditional variables can be defined with setter methods and you can easily fetch with getters.
public class ShiftLog {
private Object userUid;
private String companyName;
private boolean workedForAgent;
private String agentName;
private Date shiftStart;
private Date shiftEnd;
private boolean breakTaken;
private Date breakStart;
private Date breakEnd;
private boolean isTransportJob;
private String transportCompanyName;
private String vehicleRegistration;
public ShiftLog(String companyName, Date shiftStart, Date shiftEnd) {
this.userUid = FirebaseAuth.getInstance().getCurrentUser().getUid();
this.companyName = companyName;
this.shiftStart = shiftStart;
this.shiftEnd = shiftEnd;
}
public boolean isWorkedForAgent() {
return workedForAgent;
}
public void setWorkedForAgent(boolean workedForAgent) {
this.workedForAgent = workedForAgent;
}
public String getAgentName() {
return agentName;
}
public void setAgentName(String agentName) {
this.agentName = agentName;
}
public boolean isBreakTaken() {
return breakTaken;
}
public void setBreakTaken(boolean breakTaken) {
this.breakTaken = breakTaken;
}
public Date getBreakStart() {
return breakStart;
}
public void setBreakStart(Date breakStart) {
this.breakStart = breakStart;
}
public Date getBreakEnd() {
return breakEnd;
}
public void setBreakEnd(Date breakEnd) {
this.breakEnd = breakEnd;
}
public boolean isTransportJob() {
return isTransportJob;
}
public void setTransportJob(boolean isTransportJob) {
this.isTransportJob = isTransportJob;
}
public String getTransportCompanyName() {
return transportCompanyName;
}
public void setTransportCompanyName(String transportCompanyName) {
this.transportCompanyName = transportCompanyName;
}
public String getVehicleRegistration() {
return vehicleRegistration;
}
public void setVehicleRegistration(String vehicleRegistration) {
this.vehicleRegistration = vehicleRegistration;
}
}

Dynamodb versioing with dynamodb mapper is not working as expected

I am getting conditionalcheckfailed exception when trying to save/update items using dynamodb mapper.
Can anyone please share snippet of code using java that can demonstrate how versioning and optimistic locking can be implemented successfully?
Tried not setting version at all!!
Tried adding a record to table, and then doing read before save.
Nothing woked!! I continue to get ConditionalCheckFailed Exception.
Only thing works is if I set the config to COBBLER!! but that's not what I want as I need optimistic locking for my data.
DB item class---
#DynamoDBTable(tableName="Funds")
public class FundsItem {
private String id;
private String auditId;
private Long version;
private String shopId;
private String terminalId;
private String txId;
#DynamoDBHashKey(attributeName = "Id")
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
#DynamoDBRangeKey(attributeName = "AuditId")
public String getAuditId() {
return auditId;
}
public void setAuditId(String auditId) {
this.auditId = auditId;
}
#DynamoDBVersionAttribute(attributeName = "Version")
public Long getVersion() { return version; }
public void setVersion(Long version) { this.version = version; }
#DynamoDBAttribute(attributeName = "ShopId")
public String getShopId() {
return shopId;
}
public void setShopId(String shopId) {
this.shopId = shopId;
}
#DynamoDBAttribute(attributeName = "TerminalId")
public String getTerminalId() { return terminalId; }
public void setTerminalId(String terminalId) {
this.terminalId = terminalId;
}
#DynamoDBAttribute(attributeName = "TxId")
public String getTxId() {
return txId;
}
public void setTxId(String txId) {
this.txId = txId;
}
}
Code to save new item -----
public long addFunds(FundsRequest request){
FundsItem dbItem = new FundsItem();
String Id = request.getShopId().trim() + request.getTerminalId().trim();
String V0_Audit_Rec = "V0_Audit_" + Id;
//save V0 item.
dbItem.setVersion((long) 1);
dbItem.setId(Id);
dbItem.setAuditId(V0_Audit_Rec);
dbItem.setShopId(request.getShopId().trim());
dbItem.setTerminalId(request.getTerminalId().trim());
dbItem.setTxId(request.getTxId().trim());
mapper.save(dbItem);
}
Pls check the snippet above - This is a new empty table.
hashkey - id, rangekey - auditId, VersionField - version.
I just want to be able to add a new record that's why not doing any read before saving a new item. If I can get this simple case i.e. adding a new /first record to the dynamodb table work, I can implement rest of the use cases too.
In general:
Never set your version, the SDK will initialise this if required.
Always try and load an item with your key first. If null is returned, create the item and save it. Else update the returned item and save it.
I know you mentioned you've tried the above. If its truely an empty table your code should work OK (minus the setting of the version).
A couple of things I would also do:
Don't set your version field with a custom attribute name. In theory this should be fine, but for the sake of making your code the same as the AWS examples, I would remove this, at least until you have it working.
Although I think you need to remove the setting of the version, I note you are casting to a long, not a Long. Again, unlikely to be an issue but just something to eliminate at least. i.e. if you insist of setting version use new Long(l).

Storing BLOB in Database Spring Hibernate Persistence

This is a problem iv encountered and tried most of the solutions that i have been offered so far and little seems to work , the problem making this harder to fix is for some reason the hibernate session wont print its details to the log providing me with very little in terms of error tracing. I want to upload a string of Json converted to a blob into the database . If anyone knows where i am going wrong or can provide and pointers this would be great as im struggling to solve this alone.
#Entity
#Table(name="workout")
public class Workout implements Serializable{
private static Logger logger = Logger.getLogger(Workout.class);
#Id
#Column(name="workout_id")
private int workout_id;
#Column(name="username")
private String username;
#Column(name="added_date")
private String added_date;
#Lob
#Column(name="workout")
Blob workout;
public int getWorkout_id() {
return workout_id;
}
public void setWorkout_id(int workout_id) {
this.workout_id = workout_id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getAdded_date() {
return added_date;
}
public void setAdded_date(String added_date) {
this.added_date = added_date;
}
public Blob getWorkout() {
return workout;
}
public void setWorkout(Blob workout) {
this.workout = workout;
}
}
method from Service that trys to upload
public String uploadWorkout(String json){
Workout w = new Workout();
w.setUsername("cmac458");
DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
Date date = new Date();
w.setAdded_date(date.toGMTString());
w.setWorkout(getBLOBfromJSON(json));
w.setWorkout_id(4);
workoutDao.getSession().save(w);
return "done";
}
I am using the basic hibernatetemplate.save(entity) that works in other parts of my application.
Any help here is much appreciated.
Thanks
Chris
I use byte[] instead ob Blob type, and that works fine.
#Lob
#Column(nullable = false, length = 2097152)
private byte[] data;

Categories