I am working on a small app in which I want to have an SQLite DB to work locally but have it synced with parse.com.
I am trying to follow the example at https://github.com/ntoskrnl/DataSync but I am having problems with this statement:
// obtain our DatabaseHelper object
SyncDatabaseHelper dbHelper = OpenHelperManager.getHelper(context, DatabaseHelper.class);
// create and initialize SyncHelper object
SyncHelper syncHelper = new SyncHelper(dbHelper);
syncHelper.setUserId(ParseUser.getCurrentUser().getObjectId());
syncHelper.setLastSyncDate(new Date(lastSyncDate));
SyncDatabaseHelper does not seem to be a known class.
There was a mistake in the README file. Thank you for mentioning, I updated it.
As njzk2 pointed out, it is better to open an issue on GitHub.
First of all, the database operations shouldn't be performed on the UI thread. You should do all your work with DB in background thread (such as AsyncTask or Bolts task).
Secondly, you there is no need in additional updatedAt field in your entity-class. SyncEntity that you extend already has the field syncDate mapped to the updatedAt of ParseObject. Also, you cannot directly change updatedAt in your ParseObject, so the library uses reflection for this.
Just use syncDate instead of updatedAt. When you create new category or update existing one, you should set or update syncDate field of your entity - just as you did with updatedAt in your code.
Here is how I use the library in one of my projects:
ProgressDialog pDialog = null;
public void sync() {
// show progress dialog
pDialog = new ProgressDialog(getActivity());
pDialog.setIndeterminate(true);
pDialog.setMessage("Synchronizing session data...");
pDialog.setCancelable(false);
pDialog.show();
// get last sync date from preferences
final Date lastSyncDate = new Date(mPrefHelper.getLong(
Constants.APP_LAST_SYNC_TIMESTAMP, 0L));
// Prepare SyncHelper
final SyncHelper syncHelper = new SyncHelper(HelperFactory.getHelper());
syncHelper.setUserId(ParseUser.getCurrentUser().getObjectId());
syncHelper.setLastSyncDate(lastSyncDate);
Task.callInBackground(
new Callable<Long>() {
#Override
public Long call() throws Exception {
// save sync timestamp
long sync = System.currentTimeMillis();
syncHelper.synObjects(CardioSessionEntity.class, true, new SyncCallback());
return sync;
}
}
).continueWith(
new Continuation<Long, Object>() {
#Override
public Object then(Task<Long> task) throws Exception {
if (task.isFaulted()) {
Log.w(TAG, "Sync failed with exception", task.getError());
if (getActivity() != null) {
Toast.makeText(getActivity(), "Sync failed.",
Toast.LENGTH_SHORT).show();
}
} else if (task.isCompleted()) {
// save new sync timestamp to Preferences
mPrefHelper.putLong(Constants.APP_LAST_SYNC_TIMESTAMP, task.getResult());
}
// refresh UI and hide progress dialog
refreshSessionList();
if (pDialog != null) {
pDialog.dismiss();
}
pDialog = null;
return null;
}
},
Task.UI_THREAD_EXECUTOR);
}
I store last sync timestamp in Android preferences and use Bolts task to do synchronization in background. The synchronisation is performed by calling syncHelper.syncObjects(). I provide the entity-class, a userAware flag and SyncCallback object (can be null).
In the app, I have Sync button. Every time user clicks on it, I call the method sync() shown above. For each updated object the corresponding methods of SyncCallback are called.
For your particular case, I assume you don't need userId, so you can call
// Do this in the background
// save sync timestamp
long sync = System.currentTimeMillis();
// sync Category.class
syncHelper.synObjects(Category.class);
// return sync timestamp to save it in the later
return sync;
Call it from within AsyncTask.doInBackground() whenever you want to perform synchronization.
The syncDate is optional. If you don't provide lastSyncDate, the library will request all objects from the server.
Because the library doesn't support (and is not aware of) the relationships between your database entities, if you synchronize multiple entity-classes, you must handle save operations using SyncCallback. Its methods onSaveLocally() and onSaveRemotely() will be invoked just before persist/update (in local DB) or save (to parse.com).
Related
I have an android app with Azure Mobile Services and implemented Offline Sync. The app works well but when syncing data it seems not to complete so there is always a few rows on tables which have not synced?
Anyone have any ideas what the problem might be. I believe that on the next try it would finish where it left off or am I wrong?
Thanks in advance
The app works well but when syncing data it seems not to complete so there is always a few rows on tables which have not synced?
I would recommend you use fiddler to capture the network traces when handling the sync operations.
For Incremental Sync, the request would be as follows:
Get https://{your-app-name}.azurewebsites.net/tables/TodoItem?$filter=(updatedAt%20ge%20datetimeoffset'2017-11-03T06%3A56%3A44.4590000%2B00%3A00')&$orderby=updatedAt&$skip=0&$top=50&__includeDeleted=true
For opting out of incremental sync, you would retrieve all records without the filter updatedAt.
Get https://{your-app-name}.azurewebsites.net/tables/TodoItem?$skip=0&$top=50&__includeDeleted=true
Note: If there are too many items, the SDK would send multiple requests to pull all items that match your given query from the associated remote table. Also, you need to make sure you specify the includeDeleted() in your query.
In summary, you need to make sure that all items could be retrieved via the above requests. Additionally, if the pull operation has pending local updates, then the pull operation would first execute a push operation. So, I assume that you could catch the exception when calling pull operation for handling the conflict resolution.
Bruce's answer is fine but I used a slightly different method without the need to use fiddler.
I change my connection from this
mClient = new MobileServiceClient("[AZUREWEBSITE]", cntxall);
mClient.setAndroidHttpClientFactory(new MyOkHttpClientFactory());
To this
mClient = new MobileServiceClient("[AZUREWEBSITE]", cntxall).withFilter(
new ServiceFilter() {
#Override
public ListenableFuture<ServiceFilterResponse> handleRequest(ServiceFilterRequest request, NextServiceFilterCallback nextServiceFilter) {
// Get the request contents
String url = request.getUrl();
String content = request.getContent();
if (url != null) {
Log.d("Request URL:", url);
}
if (content != null) {
Log.d("Request Content:", content);
}
// Execute the next service filter in the chain
ListenableFuture<ServiceFilterResponse> responseFuture = nextServiceFilter.onNext(request);
Futures.addCallback(responseFuture, new FutureCallback<ServiceFilterResponse>() {
#Override
public void onFailure(Throwable e) {
Log.d("Exception:", e.getMessage());
}
#Override
public void onSuccess(ServiceFilterResponse response) {
if (response != null && response.getContent() != null) {
Log.d("Response Content:", response.getContent());
}
}
});
return responseFuture;
}
}
);
This is the logging method for Azure connections and shows the request in the log.
I am building an SDK.
The Scenario
From the app, the developer passes the JsonObject and URL in a method
and inside the SDK.
I add those values in SQLite database and start a JobScheduler.
The Jobscheduler takes the request at 0 indexes out of the database
executes it.
When I get the response, I delete that request from the database and now the request at 1 index comes to the 0 index and again I execute the same code where 0th index request is fired.
The Problem
When I get the response from a server inside the SDK, I need to send it to the developer using a callback.
I can take the callback as an argument when I take the JSON and URL from user, but I don't know how to proceed further because I cannot store it into the database
Suppose I have 5 requests in the database and scheduler executes it one by one, I don't know how to send the response back to the developer. I can not pass context in the jobscheduler. The only way to do that is I get the corresponding context for each row (request) in the database.
What I tried
I tried using a LocalBroadcastManager, but I can not create a generic class and get it's onreceive() method implemented, also the context passing was a problem
Tried using Realm as a database so that I can add Context type and use my model, but it is not working as Context is not supported.
Storing Activity name in the database with other details and then driving out the class from it and activity from class. And then typecasting the activity into my callback. but it throws ClassCastException as the class it tries to derive from the Name is on the developer's app and not in my SDK
..
The App's MainActivity Code
sdkClass.queueRequest(jo.toString(),"url1",12,"MainActivity");
sdkClass.queueRequest(jo2.toString(),"url2",13,"MainActivity");
sdkClass.queueRequest(jo.toString(),"url3",14,"MainActivity");
sdkClass.executeQueue();
The SDK class which adds and executes code
public void queueRequest(String payload, String URL, int requestId, String identifier){
RequestsDatabase database = new RequestsDatabase(context);
database.insertItem(TxnType,payload,url, String.valueOf(requestId), identifier);
}
public JobScheduler getQueueInstance(Context context){
if(jobScheduler==null){
jobScheduler = (JobScheduler)context.getSystemService(JOB_SCHEDULER_SERVICE);
}
return jobScheduler;
}
public void executeQueue(){
getQueueInstance(context);
ComponentName jobService = new ComponentName(context.getPackageName(), MyJobService.class.getName());
JobInfo jobInfo = new JobInfo.Builder(1,jobService).setPersisted(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY). build();
jobScheduler.schedule(jobInfo);
}
MyJobServiceCode
#Override
public boolean onStartJob(JobParameters params) {
// UtilityMethods.showToast(this,params.getExtras().getString("json"));
Toast.makeText(this,"test", Toast.LENGTH_SHORT).show();
database = RequestsDatabase.getInstance(this);
ALlRequests = database.getAllItemId();
executeCall();
return false;
}
public void executeCall(){
ALlRequests = database.getAllItemId();
if(ALlRequests.size()>0) {
requestModel = ALlRequests.get(0);
try {
String payload = getPayload(this, requestModel.getTxnType(), requestModel.getPayload());
SilverLineRequest req = new SilverLineRequest(this, requestModel.getUrl(), payload, Integer.parseInt(requestModel.getRequestId()), this);
req.executeToServer();
Toast.makeText(getApplicationContext(), "requested", Toast.LENGTH_LONG).show();
} catch (JSONException e) {
e.printStackTrace();
}
}
else{
Toast.makeText(this, "Queue empty", Toast.LENGTH_LONG).show();
}
}
#Override
public void onSuccess(String response, int requestId) {
if(requestId ==Integer.parseInt(requestModel.getRequestId())){
Toast.makeText(this,"responded", Toast.LENGTH_SHORT).show();
database.deleteItem(requestModel.getTxnType());
// HERE I WANT TO SEND THE USER RESPONSE LIKE
// listener.onTransactionSuccess(response)
// BUT I DON'T HAVE 'listener' SO IT SHOWS NULLPOINTER
SDKClass sl = new SDKClass(this);
sl.executeQueue();
}
}
Any help will be blessed right now.
You store requests in database, so it can has unique id (autoincrement).
Then you can store callbacks in memory with relation request id -> callback.
On response you can call it.
If you want, return id to developers. Then they can bind and unbind and get results when they need, even the response had got yesterday.
Take a look on TransferUtility from Amazon AWS SDK for example.
In my android app, after sometime (hour or so.. not something determined) the connection and response to Google-AppEngine takes very long, something like 10 seconds or more.
After the first connection all other enpoint requests are done pretty quickly and this is why I believe this is SW issue and not internet connection issue.
Should I establish a 'dummy' connection as the app is loaded ?
Here is a sample code of an AsyncTask which tried to get User entity from AppEngine endpoint :
private class getUser extends AsyncTask<Void, Void, Boolean> {
long mTaskUserId = Constants.USER_ID_NO_ID_INFDICATOR;
String mIdInPlatform = Constants.USER_ID_NO_ID_INFDICATOR.toString();
Long mServerScore;
Context mContext;
String mUserName;
getUser(String idInPlatform, String userName, Context c) {
mIdInPlatform = idInPlatform;
mUserName = userName;
mContext = c;
}
#Override
protected Boolean doInBackground(Void... params) {
Userendpoint.Builder builder = new Userendpoint.Builder(
AndroidHttp.newCompatibleTransport(), new JacksonFactory(), null);
builder = CloudEndpointUtils.updateBuilder(builder);
Userendpoint endpoint = builder.build();
try {
User user = endpoint.getUser().execute();
} catch (IOException e) {
Log.e(TAG, "Error getting user details from server ", e);
return false;
}
this.mUserName = user.getUserName();
this.mServerScore = user.getScore();
this.mTaskUserId = user.getId();
return true;
}
#Override
protected void onPostExecute(Boolean result) {
if (result) {
setUserFacebookIdInPreferences(mIdInPlatform, mContext);
setUserIdInPreferences(this.mTaskUserId, mContext);
setScoreInPreferences(this.mServerScore, mContext);
setUserNameInPreferences(this.mUserName, mContext);
} else {
Toast.makeText(mContext, R.string.string_login_failed, Toast.LENGTH_SHORT).show();
}
// Restart login activity.
moveToLoginActivity(result);
super.onPostExecute(result);
}
}
Your application in Google App Engine uses two types of server instances: Dynamic instances and Resident instances. The difference is that dynamic instances are created in demand to serve traffic requests. Resident instances are always on.
When traffic stops, all your dynamic instances will shut down to save resources (and help you save money). The first time a request hits the server, a new dynamic instance will spin off to serve that request. The process of starting a new instance might take some time.
This is very likely what you are seeing in your application. To avoid that initial latency you can do two different things:
1) Optimize the time it takes for your code to load up.
2) Set up a Resident instance.
You can find more information on the Google documentation here:
https://developers.google.com/appengine/docs/adminconsole/instances#Introduction_to_Instances
You can warm-up your instances so that they're live before any query hits them - saving you this 10s delay. See documentation at:
https://developers.google.com/appengine/docs/adminconsole/instances#Warmup_Requests
I have a problem with synchronizing results from AsyncTask when needed to check if input is vaild.
My application asks user to type in some promotion code into EditText. I want to label promotion code as valid if promotion code is in database. If promotion code entered is not in database, it is labeled as invalid.
This is code for button listener
// Listens for Add button presses
addButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
String promo_code = (String) PromotCodeEditText.getText().toString();
// Start AsyncTask that will store true or false in checkCodeInDatabase variable
// depending if PromoCode is in database. We use checkCodeInDatabase in else if check below.
new CheckCodeDatabase().execute(promo_code);
// Check if Promo Code that user typed is in database
// checkCodeInDatabase is false if course is not in database
if (!checkCodeInDatabase) {
// Display No code in database alert dialog
showMsgDialog(getString(R.string.noCodeDBTitle),
getString(R.string.noCodeDBMessage));
courseCodeEditText.setText("");
} else {
// Add promo code user typed in EditText
addCode(promo_code);
}
}
});
My idea is that when user presses ADD button, to add promo code, I start AsyncTask which performs a query on database. AsyncTask stores true or false in static variable checkCodeInDatabase.
Here's code for AsyncTask:
private class CheckCodeDatabase extends AsyncTask<String, Object, Boolean> {
DataBaseHelper myDbHelper = new DataBaseHelper(MainActivity.this);
#Override
protected Boolean doInBackground(String... params)
{
try {
myDbHelper.createDataBase();
} catch (IOException ioe) {
throw new Error("Unable to create database");
}
try {
myDbHelper.openDataBase();
} catch (SQLException sqle) {
throw sqle;
}
// This returns true or false if params[0], which is a promo code,
// is in in PromoCodes table in database.
return myDbHelper.checkCourseInDatabase(params[0]);
}
#Override
protected void onPostExecute(Boolean result) {
// Store true or false in checkCodeInDatabase which is a static variable.
// This variable will be used to check if code is valid in add button listener.
checkCodeInDatabase = result;
myDbHelper.close();
}
Problem:
Problem is that AsyncTask does not store value into checkCodeInDatabase variable on time. Meaning, if statement which is performed in add button listener, value of checkCodeInDatabase is the old value because AsyncTask does not have enough time to perform database query and update checkCodeInDatabase variable before if statement is executed in add button listener.
So, how to make add button listener wait for AsyncTask to update checkCodeInDatabase and then perform if check.
Thanks in advance!
p.s. I'm new to android development. I read in some book on Android development that any queries on database should be performed in AsyncTasks, to avoid unresponsive app. Maybe it's impossible to achieve what I want with AsyncTasks. Any suggestions are welcome.
You should delegate the whole
if (!checkCodeInDatabase) {
// Display No code in database alert dialog
showMsgDialog(getString(R.string.noCodeDBTitle),
getString(R.string.noCodeDBMessage));
courseCodeEditText.setText("");
} else {
// Add promo code user typed in EditText
addCode(promo_code);
}
block into you AsyncTask's onPostExecute.
Doing that you are sure that you have fetched the result AND that you are running on the ui thread.
I am developing a small application with lot of db operations in it. Basically its a tab activity, having 3 tabs in it. each tab will show data by parsing xml files.
3 tab activities are using same db object, so that am able to see only 2 tabs filled with data, where 3rd one always showing progress dialog which i created.
following is my async task where am populating db.
protected void onPreExecute() {
super.onPreExecute();
sdbop = new SDBOperations(context);
hdbop = new HDBOperations(context);
pdbop = new PDBOperations(context);
sdbop.open();
hdbop.open();
pdbop.open();
}
#Override
protected Void doInBackground(UpdateXML... statuslist) {
for(UpdateXML status:statuslist){
if(status==UpdateXML.C){
checkStatusAndPopulateDB(status,null);
continue;
}
String url = getUpdateURL(status);
String xml = XMLParser.getXmlFromUrl(url);
if(xml!=null)
checkStatusAndPopulateDB(status,xml);
}
return null;
}
private void checkStatusAndPopulateDB(UpdateXML status, String result) {
switch (status) {
case S:
sdbop.open();
dropTable();
insertIntoSTable(result);
sdbop.close();
updateSList();
break;
case H:
hdbop.open();
dropHTable(hdbop);
insertIntoHTable(hdbop,result);
hdbop.close();
updateHList();
case P:
pdbop.open();
dropPTable(pdbop);
insertIntoPTable(pdbop, result);
pdbop.close();
updatePList();
break;
case C:
updateLView();
break;
default:
break;
}
so every thing is working ok. but db object is accessable by 3 tasks continuously. so that one of my tab is always in waiting stage and view is also in progress.
So, if there is one task is accessing db, others should be in waiting.
how to handle this situation?
I have not closed some cursors in my datag base operations. so that database objects not getting released after doing insertion and updation operations. After closing all cursors, my application working fine.