I'm developing an android app that uses SQLite as the local database. The app syncs data obtained from a web api and stores it in the local database. All the model classes have their ID property set as Primary key and Auto incremented so I can manually enter data without having to specify the ID. The issue is when I insert the data from the API into the SQlite, the ID of the object is ignored and Sqlite gives the object a new ID. I want the data stored with the same ID as the object being stored.
The web api returns the object lists that have their ID type long however the SQLite objects have their primary keys as int. Is this the reason why the ID values is not getting stored because their data types don't match? I can't change the datatype in my SQL database where the data comes from as there are hundreds of tables in it. Is there a way around it?
This is the Code to inserts or updates data in my local DB:
}
public async Task<string> insertUpdateVideoData(Video_Struct data)
{
try
{
var db = new SQLiteAsyncConnection(dbPath);
var m = GetVideos();
if (await db.FindAsync<Video_Struct>(f => f.VideoID == data.VideoID) != null)
{
await db.UpdateAsync(data);
}
else
{
if (await db.InsertAsync(data) != 0)
{
await db.UpdateAsync(data);
}
}
return "Single data file inserted or updated";
}
catch (SQLiteException ex)
{
return ex.Message;
}
}
This is the code to get data objects from the API:
public async Task<List<Video_Struct>> GetVideoData()
{
List<Video_Struct> vids = new List<Video_Struct>();
WebClient mClient = new WebClient();
var output = await mClient.DownloadDataTaskAsync(new Uri(GlobalVariables.host + "/api/media/getmedia"));
var json = Encoding.UTF8.GetString(output);
vids = JsonConvert.DeserializeObject<List<Video_Struct>>(json);
return vids;
}
If your local DB is a cache for web data and external DB gives you unique IDs, don't use auto increment in scheme, just re-use external IDs.
Actually, you can have a complex (compound) primary key, it depends on data unique properties.
If you do not work with your data as structured set you can try gson+SharedPreferences. Just don't forget to override equals and hashcode for your data models.
Datatype in not an issue, because sqlite uses INTEGER type.
Related
Hi I am pretty new to Firebase real time database and this is my first project. Sorry if this is a stupid question.
I am saving my data as follows.
firebase database structure:
Now I want to retrieve all parent chat ids on which the student is participating, using the student_id variable.
I tried as per this SO question and this structure database and retrieve data documentation, but its not retrieving values. Anybody have an idea?
I would suggest saving the chatroom IDs your students are in in a separate location. For example:
Path:
“/users/$uid/chatrooms”
Data:
{
0: 350,
1: 423
}
Thus you could retrieve the chat room ids first before getting the chatroom data.
import { initializeApp } from “firebase”;
import { getDatabase, get, set, ref } from “firebase/database”;
const userChatroomIdsRef = ref(db, ‘/users/${uid}/chatrooms‘);
get(userChatroomIdsRef).then(result => {
const chatroomIds = result.val();
if (!(chatroomIds && chatroomIds instanceof Array)) return; // firebase will return null if its an empty array.
const getChatroomInfoPromises = chatroomIds.map(id => get(ref(db, ‘/chat/${id}/${uid}’)).then(result => result.val());
Promise.all(getChatroomInfoPromises).then(chatroomInfoArray => { YOUR LOGIC HERE });
});
Removing/adding students from/to chatrooms would now be simple as you could just change the array of chatroomIds.
const userChatroomIdsRef = ref(db, ‘/users/${uid}/chatrooms‘);
get(userChatroomIdsRef).then(result => {
const oldIds = result.val();
const newChatroomIds = oldIds.filter(id => id !== ID TO DELETE);
return set(userChatroomIdsRef, newChatroomIds)
});
This is of course assuming that you know the uid of your student_id. If you do not know what uid each student_id has, you must must store a reference. I would suggest saving all student info in the “/users/$uid/” directory. Here you could save the studentId so you can programmatically use it.
In all other firebase logic I would try to use the native firebase uid for querying. This will make your life easier.
It’s always good the keep information organized on the database so your logic is simple.
Please check my code for syntax errors; I wrote this on an iPhone.
I have a method which accepts an OValue:
getResults(OValues values)
Inside the method are the ff.
ORecordValues value = new ORecordValues();
value.put("order_partner_id", orderline.getPartner(values));
value.put("product_id", orderline.getProduct(values));
value.put("product_uom_qty",values.getInt("product_uom_qty"));
value.put("price_unit", values.getInt("product_uom_qty"));
value.put("discount",values.getInt("product_uom_qty"));
orderline.getServerDataHelper().createOnServer(value);
Is it possible to insert directly to Odoo's server without saving it to android's database?
or any alternative ways to successfully insert data to Odoo server?
In ServerDataHelper java class:
public int createOnServer(ORecordValues data) {
OdooResult result = mOdoo.createRecord(mModel.getModelName(), data);
return result.getInt("result");
}
You need to pass server record id when creating records on the server.
Pass local row id(stored in _id column) to the selectServerId method to get the server id (stored in id column):
Check the following example:
ResPartner resPartner = new ResPartner(getApplicationContext(), null);
ProductProduct productProduct = new ProductProduct(getApplicationContext(), null);
...
ORecordValues value = new ORecordValues();
int partner_id = resPartner.selectServerId(values.getInt("partner_id"));
int product_id = productProduct.selectServerId(values.getInt("product_id"));
value.put("order_partner_id", partner_id);
value.put("product_id", product_id);
value.put("product_uom_qty",values.getInt("product_uom_qty"));
value.put("price_unit", values.getInt("product_uom_qty"));
value.put("discount",values.getInt("product_uom_qty"));
orderline.getServerDataHelper().createOnServer(value);
Is there an elegant way to add a batch of new objects from JSON, taking into consideration that the new bunch might contain values that already in DB and that DB must contain only unique values?
Why not using the same id in the JSON object?, check that a unique id is being sent from the server and prepare a method that checks out for the id if it exists.
//Check if item exists already with id
public boolean checkIfExists(String id){
RealmQuery<Data> query = realm.where(Data.class)
.equalTo("id", id);
return query.count() != 0;
}
I'm looking for a way to access a newly created local ParseObject which hasn't yet synced to the Parse cloud server. Since there is no objectId value there's no way to query for the objectId through the local datastore and it appears the localId (which looks like it creates a unique identifier locally) is locked down (otherwise this would be a non-issue as I could use my Content Provider to take care of the details). Since the ParseObject class isn't Serializable of Parcelable I can't pass it through an Intent. To note the complexity of my task I have I have 3 levels of ParseObjects (ParseObject > Array[ParseObjects] > Array[ParseObjects]). Essentially I'm looking to see if Parse has full offline capabilities.
TL:DR
Basically I want to be able to access a single ParseObject in a different Activity as soon as it's created. Does this problem have a practical application with Parse and ParseObjects or am I going to have to implement some serious work arounds?
I believe ParseObjects are serializable, so put them into a Bundle and then put that Bundle into an Intent
in the current activity
Intent mIntent = new Intent(currentActivityReference, DestinationActivity.class);
Bundle mBundle = new Bundle();
mBundle.putSerializable("object", mParseObject);
mIntent.putExtras(mBundle);
startActivity(mIntent);
in the destination activity
retrieve the intent with getIntent().getExtras(), which is a Bundle object, so there is a getter for the serializable .getSerializable("object") but you will have to cast it to (ParseObject)
So I was able to keep everything within the confines of the structures I already have in place to take care of this problem (a sync adapter and the Parse API). Basically all I had to do was leverage Parse's existing "setObjectId" function.
NOTE: This only works with an existing Content Provider / SQLiteDatabase
I created a temporary unique ID for the new ParseObject to be stored locally. This unique value is based off of the max index number in the Content Provider I'm storing my objects (for my Sync Adapter).
//query to get the max ID from the Content Provider (used with the sync adapter)
Cursor cursor = context.getContentResolver().query(
WorkoutContract.Entry.CONTENT_URI,
new String[]{"MAX(" + WorkoutContract.Entry._ID + ")"},
null, null, null
);
long idx = 1; //default max index if there are no records
if (cursor.moveToFirst())
idx = cursor.getInt(0) + 1;
final long maxIndex = idx;
cursor.close();
//this is the temporary ID used for storing, a String constant prepended to the max index
String localID = WorkoutContract.LOCAL_WORKOUT_ID + maxIndex;
I then used the pin() method to store this ParseObject locally and then made an insert into the Content Provider to not only keep the ID in the table to iterate the max index in the table.
//need to insert a dummy value into the Content Provider so the max _ID iterates
ContentValues workoutValues = new ContentValues();
//the COLUMN_WORKOUT_ID constant refers to the column which holds the ParseObjects ID
workoutValues.put(WorkoutContract.Entry.COLUMN_WORKOUT_ID, localID);
context.getContentResolver().insert(
WorkoutContract.Entry.CONTENT_URI,
workoutValues);
Then I created another dummy ParseObject with all the same attributes as the one with the local ID (without the local ID). This ParseObject was then saved to the server via the saveEventually() function. (Note: This will create 2 local copies or your ParseObject. To leave the blank copy out of queries simply leave out ParseObjects with null object IDs).
query.whereNotEqualTo("objectId", null);
In the saveEventually() function there needs to be a callback which replaces the old (local) ParseObject as well as the localID value in the Content provider. In the SaveCallback object replace the server returned ParseObject's attributes with the local ones (to account for any changes made during the server query). Below is the full code for the SaveCallback where the tempObject is the one sent to the Parse server:
tempObject.saveEventually(new SaveCallback() {
//changes the local ParseObject's ID to the newly generated one
#Override
public void done(ParseException e) {
if (e == null) {
try {
//replaces the old ParseObject
tempObject.put(Workout.PARSE_FIELD_NAME, newWorkout.get(Workout.PARSE_FIELD_NAME));
tempObject.put(Workout.PARSE_FIELD_OWNER, ParseUser.getCurrentUser());
tempObject.put(Workout.PARSE_FIELD_DESCRIPTION, newWorkout.get(Workout.PARSE_FIELD_DESCRIPTION));
tempObject.pin();
newWorkout.unpinInBackground(new DeleteCallback() {
#Override
public void done(ParseException e) {
Log.i(TAG, "Object unpinned");
}
});
} catch (ParseException e1) {
e1.printStackTrace();
}
//update to content provider with the new ID
ContentValues mUpdateValues = new ContentValues();
String mSelectionClause = WorkoutContract.Entry._ID + "= ?";
String[] mSelectionArgs = {Long.toString(maxIndex)};
mUpdateValues.put(WorkoutContract.Entry.COLUMN_WORKOUT_ID, tempObject.getObjectId());
mUpdateValues.put(WorkoutContract.Entry.COLUMN_UPDATED, tempObject.getUpdatedAt().getTime());
context.getContentResolver().update(
WorkoutContract.Entry.CONTENT_URI,
mUpdateValues,
mSelectionClause,
mSelectionArgs
);
}
}
});
To get the local ParseObject in another Activity just pass the local objectId in an Intent and load it. However, the index of the ParseObject on the Content Provider needs to be passed as well (or it can be retrieved from the unique local ID) so if the ParseObject is ever retrieved again you can check the Content Provider for the updated Object ID and query the correct ParseObject.
This could use a bit of refinement but for now it works.
In the greendao FAQs it says "Starting from greenDAO there’s limited support for String primary keys." http://greendao-orm.com/documentation/technical-faq/
I can't find anywhere that says how to do this.
I am using Guids as my primary key in a server application, and want to be able to generate new data remotely from an android device and upload this back to the server. The database on the android device is in sqlite and uses greenDAO to generate POJOs and data access layer. I am using Guids to avoid primary key collisions when data is uploaded to the server. I am storing the Guids as strings.
There is some more advice on the greendao website that says I should create a secondary field holding the string and still use the long primary key favoured by greendao, but this means that I have to reconnect all my database relationships when I import data from the server to the app which is a pain. Would much rather just continue to use the string primary keys if that is possible.
Can anybody tell me how to do this?
Here is some example code...
In my generator (I've removed most of the fields for clarity):
private static void addTables(Schema schema)
{
Entity unit = addUnit(schema);
Entity forSale = addForSale(schema);
Property unitIntId = forSale.addLongProperty("unitIntId").getProperty();
forSale.addToOne(unit, unitIntId);
}
private static Entity addForSale(Schema schema)
{
Entity thisEntity = schema.addEntity("ForSale");
thisEntity.addIdProperty();
thisEntity.addStringProperty("forSaleId");
thisEntity.addFloatProperty("currentPriceSqFt");
thisEntity.addStringProperty("unitId");
return thisEntity;
}
private static Entity addUnit(Schema schema)
{
Entity thisEntity = schema.addEntity("Unit");
thisEntity.addIdProperty();
thisEntity.addStringProperty("unitId");
thisEntity.addStringProperty("name");
return thisEntity;
}
In my android application I download all the data from the server. It has relationships based on the GUID id's. I have to reattach these to the int Id's I created in the generator like this:
//Add relations based on GUID relations
//ForSale:Units
for(ForSale forSale:Globals.getInstance().forSales)
{
if (forSale.getUnitId() != null && forSale.getUnit() == null)
{
for(Unit unit:Globals.getInstance().units)
{
if (forSale.getUnitId().equals(unit.getUnitId()))
{
forSale.setUnit(unit);
break; //only need the first one
}
}
}
}
So I end up having two sets of Id's linking everything, the int one for greendao and the string (guid) one that will work when it gets uploaded back to the server. Must be an easier way!
Try this:
private static void addTables(Schema schema) {
Entity unit = addUnit(schema);
Entity forSale = addForSale(schema);
Property unitId = forSale.addStringProperty("unitId").getProperty();
forSale.addToOne(unit, unitId);
}
private static Entity addForSale(Schema schema) {
Entity thisEntity = schema.addEntity("ForSale");
thisEntity.addStringProperty("forSaleId").primaryKey();
thisEntity.addFloatProperty("currentPriceSqFt");
return thisEntity;
}
private static Entity addUnit(Schema schema) {
Entity thisEntity = schema.addEntity("Unit");
thisEntity.addStringProperty("unitId").primaryKey();
thisEntity.addStringProperty("name");
return thisEntity;
}
I don't know if the ToOne-Mapping will work with strings, though. If it doesn't you can add some methods for getting the related objects in the KEEP-SECTIONS.