Master Sync Database - android

Hi am syncing my database with server for any incremental values on click of a button. Below is the code which checks all the values and inserts if the data is missing but from android perspective is there any optimized technique to do the sync
//new fields
public void updatenewfileds(ArrayList<String> s) {
ArrayList<String> da = new ArrayList<String>();
try {
String manu = "select MANUFACTURERID from ManufacturerDesc";
Cursor cc = mDb.rawQuery(manu,null);
Log.d("Cursor count", "Count =" + cc.getCount());
if (cc != null && cc.getCount() > 0) {
if (cc.moveToFirst());
do {
da.add(cc.getString(cc.getColumnIndex("MANUFACTURERID")));
System.out.println("here got all alreday avilable ids"+ cc.getString(cc.getColumnIndex("MANUFACTURERID")));
} while (cc.moveToNext());
cc.close();
} else {
cc.close();
}
// need to add only missing data
for(int i = 0; i<da.size(); i++){
boolean flag = false;
System.out.println(flag);
for(int j=0; j<i; j++){
if(da.get(i).equals(s.get(i*2))){
flag = true;
break;
}
}
if(flag == false){
String sql = "insert into ManufacturerDesc values('"+ s.get(i*2)+"','"+ s.get(i*2+1)+"');";
System.out.println("item inserted into db"+ s.get(i*2) +"******" + s.get(i*2+1) );
mDb.execSQL(sql);
}
}
} catch (SQLException mSQLException) {
Log.e(TAG, "getTestData >>" + mSQLException.toString());
throw mSQLException;
}
}

This would be my suggestion, the [] are just to emphasize, as I might get back to it:
Design your Android database tables like: { _id, [server_id], .. your data .. }
On all your tables on the server add a [time_changed] timestamp.
Whenever your device gets synced with the server, the server should additionally send a last sync timestamp e.g. System.currentTimeMilliseconds() (letting the server do this to avoid relying on synced clocks). This timestamp is stored on the android device and used whenever requesting a new sync.
When the server receives a sync request the stored last sync timestamp is yet again handed to the server from the device. Now a simple query can extract all the relevant added data since the timestamp (minus some constant to ensure you get everything). For example SELECT * FROM Mydata WHERE (time_changed > (last_sync-5000)); 5000 being 5 seconds.
Now as you receive data from the server, remember to add the [server_id], which is just the autoincremented _id from the server. This enables you to deduce whether some of thee received rows are known (which is likely with the minus 5 seconds above).
The deduction is a simple query on the device e.g: Do I already have a row with [server_id], if not we add it, if yes then skip it.
With this method you avoid to send more and more information over time, as you only send the rows that are changed after the last sync (plus a bit more).
If you edit rows on your server, simply update time_changed again to reflect the edit . Then it will automatically be included and overwritten on the device during the next sync.
If you plan on doing a lot of database operations on the android device, I would suggest trying MotoDev, it can be plugged in to eclipse and has some nice database features. Including a database perspective and automatic generation of ContentProviders (nice classes to make database operations simple).
Giving a full explanation or guide to do all this is way out of the scope of this answer. It should merely give you an idea as to how it can be done, and if you wish to improve your syncing process, you now have some guidance.
Regarding mechanism as autoincrement and timestamp on a database, there is plenty of examples to find on the interwebz.
Happy coding :)

Related

Parse query not returning new data on onResume

I'm developing a feature where users can join a room and I'm using Parse to hold the room's data. My problem is that when a new user joins a room and an existing user of that room resumes the room's activity, the existing user doesn't see the new user.
So here's an entry for my table where a user created a room but no one's joined yet.
Here's my GameOnSession class which is an extension of the ParseObject class.
#ParseClassName("GameOnSession")
public class GameOnSession extends ParseObject {
public JSONArray getParticipants() { return getJSONArray("participants"); }
public String getNumberOfParticipants() {
int getParticipantsNumber = getParticipants().length();
return String.valueOf(getParticipantsNumber);
}
public static ParseQuery<GameOnSession> getQuery() {
return ParseQuery.getQuery(GameOnSession.class);
}
}
This is my query that I run to check the number of people in the room.
ParseQuery<GameOnSession> query = ParseQuery.getQuery(GameOnSession.class);
query.whereEqualTo("objectId", QueryPreferences.getStoredSessionId(getActivity()));
query.findInBackground(new FindCallback<GameOnSession>() {
#Override
public void done(List<GameOnSession> objects, ParseException e) {
GameOnSession sess = objects.get(0);
Log.d("GAMEONSESSION", "Current # " + sess.getNumberOfParticipants());
}
});
So it returns what I expect: 0.
Now when a new user joins the room, the entry looks like this.
Then I press a button that runs the query above. And it still returns 0, when I expect 1.
I'm not sure what's the problem here, did I set up my query wrong?
In short, when User A creates a room, the Parse query returns number of users as 0 which is expected. When User B joins User A's room, Parse query STILL returns the numbers of users as 0 and NOT 1. This is unexpected and I'm not sure how to proceed.
So I found out the cause of why my application wouldn't correctly query the database. The reason was that I called Parse to "enableLocalDatastore" so whenever I tried a new query, the application would query from the cached result.
So what I did was disable local datastore and everything's working as expected.
Funny thing was, my team and I that's building this application enable local datastore in the beginning of the development cycle (6 months ago) to test a small feature and forgot to disable it. Technical debt is a PITA.
So if your Parse query is not returning what you expect even though the database clearly shows what you've done, check that you haven't enabled your local datastore!

Android - Best way to sync SQLite with MySQL [duplicate]

This question already has answers here:
Synchronizing client-server databases
(6 answers)
Closed 8 years ago.
I'm working on a project containing web app and mobile app which records daily user's data. User are able to delete, update their data and they can use many devices to insert data.
I intend to develop this way:
User enter their data and then insert to SQLite. A service will start periodically (each 5hr or sth) to sync with MySQL using timestamp.
I did search for a sample using service and timestamp on the internet but I found nothing. It would be great if there are a sample or tutorial.
I'm new at Android and I have no idea what is the best way to develop an app like this. Thanks in advance.
----EDIT----
I consider using timestamp or synced. Which is more effective?
you could use volley library by google or any alternative libraries, it depends on how you want to send the data, the best approach is that you use JSON to make your life easier, get the data from sqlite that you like to sync with your backend and send it over JsonObjectRequest using volley, for example your request can look like this
jsonObjectRequest postForm = new JsonObjectRequest(Request.Method.POST, URL, YourJsonData,
new Response.Listener<JSONObject>() {
#Override
public void onResponse(JSONObject response) {
// here you can get your response.
}
},
new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
// here you can tell there is something went wrong.
}
});
u could add a new value which indicates whether the value has been sync or no from your local database. for example, lets say you have a table called student and this table has three columns which are ID, NAME and synced in above code when your response return success update that row synced column with true|false which indicates whether this row synced with your backend or no. and to get the data from your database you should do something like this.
public String getStudents() {
List<Student> students = new ArrayList<Student>();
String query = "SELECT * FROM student WHERE synced = 0";
SQLiteDatabase db = this.getWritableDatabase();
Cursor cursor = db.rawQuery(query, null);
if (cursor.moveToFirst()) {
do {
Student st = new Student();
st.setId(cursor.getString(cursor.getColumnIndex(ID)));
st.setName(cursor.getString(cursor.getColumnIndex(NAME)));
st.setSynced(cursor.getInt(cursor.getColumnIndex(SYNCED)));
students.add(st);
} while (cursor.moveToNext());
}
db.close();
return new Gson().toJson(students);
}
The esieast way AFAIK is to use a lib like http://loopj.com/android-async-http/ or volley lije k0sh says to send data to a PHP script that will manage you mysql data.
You'll have to write a php( or java) script in your server to receive your data ( You should write a REST API)
To select your HTTP lib, you should look here:
What is the most robust HTTP library for android?
You should really care about how you are going to sync your datas, because it could drain your battery.
Here you will learn how to optimize your updates:
https://developer.android.com/training/efficient-downloads/index.html
Hope it helps!

Failed to read row 0, column 0 from a CursorWindow which has 0 rows, 64 columns

I sometime get this error in my logcat.
Failed to read row 0, column 0 from a CursorWindow which has 0 rows, 64 columns.
First, a bit of a back story. I have an app that runs on many devices in our organisation. Primarily, it currently runs on about 20 x Samsung Note 8 devices, 2 x Samsung Note 10.1 devices and a couple of other ones. So far, the problem has only been happening on 2 of the Note 8 devices. On all of the other devices it seems to work just fine.
How the app works is that users use the app to gather information, text, photos, signature etc... and all of this is then stored/inserted into the SQLite database as a row in the submissions table. The submissions table has 64 columns to cater for each field being collected. There is a sync method that is always running (it is an AsyncTask that is executed inside of a runnable thread, so even if the app is closed, it still syncs the data in the background, unless you swipe close it from the android task manager), that syncs the data to a remote server and that checks each 10 seconds if a sync is needed, for example if a new submission was inserted, it will then start syncing that submission. When a submission is finished syncing with the server and receives a success response, it is then deleted from the device's database. So far it has been working great and thousands of submissions have been synced successfully etc. however, every now and again, I get this one error from one or two specific Note 8 tablets that reproduce this problem. I have tried many times to re create the error but it always works when I test it and I have tried every type of scenario to test it.
My code is thousands of lines, so I will try to keep it as relevant as possible. First, here is the relevant code for the runnable:
public Runnable myRunnable = new Runnable()
{
#Override
public void run()
{
count ++;
if(count >= 10)
{
android.util.Log.w(" SYNC ", "---------------");
android.util.Log.w(" SYNC ", "Checking if sync method is busy");
if(!syncBusy)
{
android.util.Log.w(" SYNC ", "Sync method OPEN");
doSync();//This will start doing sync.
count = 0;
}
else
{
android.util.Log.w(" SYNC ", "Sync method BUSY, will try again in 10 seconds");
android.util.Log.w(" SYNC ", "---------------");
}
}
if(count == 1 && !syncBusy)
{
checkSubmissionsLefttoSync();
}
mHandler.postDelayed(myRunnable, 1000);
}
};
Then, the doSync() method is where I do the upload and also where I get the error. It's over a thousands lines long, so I don't really want to post the code here. What I can say is that it works 100% for all devices except the one or two exceptions that produce the above mentioned error.
Also, I will post the part where I actually traverse the database inside of the sync method:
databaseHelper = new Handler_Database(this);
Cursor cursor2 = databaseHelper.getAllUnsyncedSubmissions();
if(cursor2.getCount() > 0)
{
if(cursor2.moveToFirst())
{
do
{
try
{
//doing lookups here and populating sync arrays
}
catch (IllegalStateException e)
{
//Do Nothing
}
catch (NullPointerException e)
{
//Do Nothing
}
}
while(cursor2.moveToNext());
}
else
{
}
}
else
{
}
cursor2.close();
databaseHelper.close();
I have noticed something else and I am not sure if it's a problem, however, when I run the app, logcat outputs the following line about 6 or 7 times:
12:48:11.115 7416 #7416 DEBUG SQLiteOpenHelper DB version : 60
I have my own warning messages when the app starts up, and those ones only display once. However, the db version and other info is being written multiple times to the logcat. Is this a problem ? Does this mean my database has some sort of bug that it creates multiple instances ?
All the apps are updated with signed apk's from the playstore. My database onUpgrade method looks like this:
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
{
// Drop older table if existed
db.execSQL("DROP TABLE IF EXISTS " + TABLE_SUBMISSIONS);
// Create tables again
onCreate(db);
}
This works fine for all the other tablets and device. They all update automatically from the playstore and continue to work as they should. However, why is logcat outputting those lines multiple times ?
Could this be part of the problem why I am getting the error as mentioned in the beginning of this post ?
Any possible insight would be gladly appreciated. I have been pulling my hair out for weeks and cannot find any fault in my code.
UPDATE 1:
I just saw this warning:
08:30:58.710 16745 #16745 WARN CursorWindow Window is full: requested allocation 307333 bytes, free space 249426 bytes, window size 2097152 bytes
I guess that this might be the root of my problems. Any ideas on how to solve this efficiently ?
UPDATE 2:
I think a solution might be to limit the columns that I retrieve from with the cursor. E.g., all the small text values/columns. Then afterwards, create a new query for each of the columns that I know takes up too much space. Do you think this is a solution ? Anyone ?
UPDATE 3:
This might work, I think I will do the large fields in separate cursors after the initial one is recycled. Something like this (I just wrote some psuedo code):
Cursor c = db.rawQuery("SELECT Column1, Column2 .... FROM table " + ... , ...);
Solution:
See my solution here ! https://stackoverflow.com/a/26797130/1518916
I finished my solution and it works fine now. Basically, the bottom line is that it is not ideal to store large data in a sqlite database on android. When working with images, rather store just the URI and keep the image on the device. HOWEVER, if you must, then I recommend to not load all image data into the same cursor, split them up and do multiple queries.
Please see my answer here https://stackoverflow.com/a/26797130/1518916
This error occurs when your images are of large size. Either store the url of the image or compress the image before storing.

Faster/Fastest way to get new sms messages when starting app - Android

I have an sqlite database that contains all the sms messages of the user, however when you start the app I need to get all the sms's that I might not have if the user decides to use a different sms app inbetween.
I'm looking for the best way to sync my database with the base android content provider.
at the moment I do an Insert or Ignore for each sms like so :
insert or ignore into sms (smsID, smsCONID, smsMSG, smsNUM, smsREAD, smsTIMESTAMP, smsTYPE, smsSHORTMSG) values (?,?,?,?,?,?,?,?);
I load the inbox and sent messages in separate asynctasks in my splash screen activity
this takes about 10s or so.
my doInBackground :
#Override
protected Void doInBackground(Void... arg0) {
int thread_id = 0;
int _id = 0;
int read;
String number;
boolean first = true;
String msg;
long timestamp;
/**
* RECUPERATION DES MESSAGES RECUS
*/
// PREPARE CURSOR
cursorInbox.moveToFirst();
while (cursorInbox.moveToNext()) {
if(first){
cursorInbox.moveToFirst();
first = false;
}
// RECUPERE THREAD_ID + ID_SMS + NUMERO DU CONTACT
thread_id = cursorInbox.getInt(cursorInbox.getColumnIndexOrThrow("thread_id"));
_id = cursorInbox.getInt(cursorInbox.getColumnIndexOrThrow("_id"));
number = cursorInbox.getString(cursorInbox.getColumnIndexOrThrow("address"));
msg = cursorInbox.getString(cursorInbox.getColumnIndexOrThrow("body"));
read = cursorInbox.getInt(cursorInbox.getColumnIndexOrThrow("read"));
timestamp = cursorInbox.getLong(cursorInbox.getColumnIndexOrThrow("date"));
// CREER LE SMS
dataManip.insert(_id, thread_id, msg, number, read, timestamp, "received",ContactsFactory.getMessageShort(msg));
i++;
publishProgress(i);
}
Log.d(TAG,"LoadActivity - Inbox Loaded");
return null;
}
Ideas for impovement?
How about registering a ContentObserver in a separate Service to listen to changes in the Android SMS provider? I'm not sure if it notifies observers on changes. But if it does, your database will always be in sync. But don't depend solely on this, as the service may be terminated at any time due to various reasons.
For the actual sync, ensure that the "_id" column in your table is the PRIMARY KEY. Query the content provider sorted by "_id". Query your own table sorted by "_id" and read all ids. Now iterate through the two sorted lists, inserting the items missing in yours and deleting the ones missing the content provider. You do want to also remove messages that were deleted using the other SMS app, don't you? The "insert or ignore" statement won't delete the missing rows for you.
if your database is indexed, than remove/disable the indexes: the inserts will be faster. Rebuild the indexes later in a new thread/task.
solution 2: don't write to database in first step just in memory( in a collection) later you will do the writings.
How about comparing the timestamps of each of the SMSes in the inbox with the latest timestamp in your database, and then only insert SMSes with a newer timestamp.
If you also start backwards, with the last SMS in the inbox, then you can break out of your loop as soon as you get to the first SMS older than your newest SMS.

Android SQLite in app billing

I have a quiz app which populates a sqlite database with questions of around 20 different categories. I want to implement in app billing so that if someone purchases Category1 for example, then these questions are added to the database and no others. Some of my questions fall within two categories so let's say Category1 and Category2.
try {
for (int n = 1; n < sqlString.length; n++) {
db.execSQL("INSERT INTO " + DATABASE_TABLE + " VALUES ("
+ sqlString[n] + ");");
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
This is my current set up in the SQLite class onCreate method. sqlString is a string array containing all my 500 questions so far.
I'm going to store whether a category has been bought in another table (but I am open to other suggestions on how to do this). I plan on creating a class which reads this database setting up boolean values of true or false whether each category has been bought. So
boolean cat1 = CheckIfCategoryHasBeenBought(category1)
etc. Then if it has been bought I will implement a method such as
boolean[] catChecker = {cat1, cat2, cat3, etc....}
SQLite info = new SQLite(this);
info.open();
info.addQs(catChecker)
//this will pass the true and false boolean values for each method then
//based on that I choose to implement or not
info.close();
However I don't know if this is even a good way to do it. I'm not sure how to check if the value has already been added (as a result of it crossing over with another category that's been bought). I was thinking a cursor would be best to check if the value is already added however how do I get the cursor to search?
The ways I've thought this could be achieved is
1) I create a string array only with the strings associated with bought questions.
2) I create an if statement within the for loop above which checks whether the string is from a bought category
3) I give the value "null" to all strings that haven't been bought then add an if statement only executing the SQL if the sqlString[n] is not null.
Do you guys have any idea how it would be best to set this up?
Have you thought about starting with a full database - i.e. containing all questions that could be purchased by anyone - and then delete those that are no longer applicable?
There are lots of ways to do this, and I think there will be more than 1 good answer. If you have a server, you can do things like return a list of authorized databases/strings/etc. You can use that to reference different tables. You also can create a table or use sharedpreferences of which tables are downloaded. The list goes on.
The server option would add a layer of security. The stored table index would be easier to pirate, but also might be easier to implement and gives you the ability to use the categories offline.

Categories

Resources