I have two observables(A, B), and I want the first to finish running before the second runs. But, that's not even the problem I'm having. The problem is that, when A is added before B, B doesn't run at all unless I place B before A then, the two runs. But, the scenario I'm in is like thus:
A - Pickup
B - Delivery
There are three types of orders. Pickup Only, Delivery Only and Pickup And Delivery. Pickups need to run before Deliveries in every situation. A Delivery only already have Pickup marked as true. A Pickup only, needs to be picked up and delivered on it being closed. Which is why I need Pickup to send all locally saved pickups first before sending deliveries. So, I did this:
Pickup
private Observable<UpdateMainResponse> getDeliveredOrders() {
String token = PrefUtil.getToken(context);
BehaviorSubject<Integer> pageControl = BehaviorSubject.create(1);
Observable<UpdateMainResponse> ret = pageControl.asObservable().concatMap(integer -> {
if (integer - 1 != deliveryUpdate.size()) {
Log.e(TAG, "DeliveredOrders: " + deliveryUpdate.size());
RealmOrderUpdate theDel = deliveryUpdate.get(integer-1);
Log.e(TAG, "DeliveryUpdate: " + theDel.toString());
DeliverOrder pickupOrder = new DeliverOrder();
pickupOrder.setUuid(theDel.getUuid());
pickupOrder.setCode(theDel.getDest_code());
pickupOrder.setDelivered_lat(theDel.getLoc_lat());
pickupOrder.setDelivered_long(theDel.getLoc_long());
return apiService.deliverOrder(theDel.getOrderId(), token, pickupOrder)
.subscribeOn(Schedulers.immediate())
.doOnNext(updateMainResponse -> {
try {
Log.e(TAG, updateMainResponse.toString());
realm.executeTransaction(realm1 -> theDel.deleteFromRealm());
} catch (Exception e) {
e.printStackTrace();
} finally {
pageControl.onNext(integer + 1);
}
});
} else {
return Observable.<UpdateMainResponse>empty().doOnCompleted(pageControl::onCompleted);
}
});
return Observable.defer(() -> ret);
}
Delivery
private Observable<UpdateMainResponse> getPickedOrders() {
Log.e(TAG, "PickedOrders: " + pickUpdate.size());
String token = PrefUtil.getToken(context);
BehaviorSubject<Integer> pageControl = BehaviorSubject.create(1);
Observable<UpdateMainResponse> ret = pageControl.asObservable().concatMap(integer -> {
Log.e(TAG, "MainPickedInteger: " + integer);
if (integer - 1 != pickUpdate.size()) {
RealmOrderUpdate thePick = pickUpdate.get(integer - 1);
Log.e(TAG, "PickedUpdate: " + thePick.toString());
PickupOrder pickupOrder = new PickupOrder();
pickupOrder.setUuid(thePick.getUuid());
pickupOrder.setCode(thePick.getSource_code());
pickupOrder.setPicked_lat(thePick.getLoc_lat());
pickupOrder.setPicked_long(thePick.getLoc_long());
return apiService.pickupOrder(thePick.getOrderId(), token, pickupOrder)
.subscribeOn(Schedulers.immediate())
.doOnNext(updateMainResponse -> {
try {
Log.e(TAG, updateMainResponse.toString());
realm.executeTransaction(realm1 -> thePick.deleteFromRealm());
} catch (Exception e) {
e.printStackTrace();
} finally {
pageControl.onNext(integer + 1);
}
});
} else {
return Observable.<UpdateMainResponse>empty().doOnCompleted(pageControl::onCompleted);
}
});
return Observable.defer(() -> ret);
}
Zipper
private Observable<ZipperResponse> batchedZip() {
return Observable.zip(getPickedOrders(), getDeliveredOrders(), (updateMainResponse, updateMainResponse2) -> {
List<UpdateMainResponse> orders = new ArrayList<>();
bakeries.add(updateMainResponse);
bakeries.add(updateMainResponse2);
return new ZipperResponse(orders);
});
}
Utilizing Zipper
public void generalUpload(APIRequestListener listener) {
batchedZip.subscribe(new Subscriber<ZipperResponse>() {
#Override
public void onCompleted() {
listener.didComplete();
unsubscribe();
}
#Override
public void onError(Throwable e) {
listener.handleDefaultError(e);
unsubscribe();
}
#Override
public void onNext(ZipperResponse zipperResponse) {
Log.e(TAG, zipperResponse.size());
}
});
}
Problem
I don't know why getDeliveredOrders() doesn't get called unless I move it to the first before getPickedOrders()
Reading through Rx Documentation for Zip I can see that it's not going to work as I expected where all of getPickedOrders() runs first before getDeliveredOrders() runs. It'll have to do it one by one. E.g: One of Pickup and then One of Delivery
Any help to understand what's going on would be appreciated. Thanks
Ok, so if I got that right:
Pickup only: need to run through the Pickup process, then they complete.
Delivery only: need to run through the Delivery process, then they complete.
Pickup and Delivery: need to run through Pickup first, then through Delivery.
On a very high level, almost preudo-code, why does this process not work?
Observable<Item> performPickup(Item item);
Observable<Item> performDelivery(Item item);
Observable<Items> items = ...;
items
.flatMap(item -> item.needsPickup() ? performPickup(item) : Observable.just(item))
.flatMap(item -> item.needsDelivery() ? performDelivery(item) : Observable.just(item))
.doOnNext(completedItem -> ...)
If you have different sources for the three types:
Observable<Item> items = Observable.merge(
pickupSource(),
deliverySource(),
pickupAndDeliverySource());
Related
For my app I have to run two operations, both being asynchronous:
read from a file ( I use this file to simulate reading from a data bus ) - async operation because I don't know "when" arrive a new
message/character on the bus. I search for a specific sequence
character, eg frame start_bytes = "xx" and the 4 following bytes are
"the data" I wait for.
read / update data to Firebase, depending on the "data" read from file - async operation due to addValueEventListener use.
I'm thinking a semaphore/mutex mechanism or a simple boolean flag that one task signal to the other one that a new data must be saved/updated to Firebase.
How can I synchronize these two operations ( by embedding them in a Task / AsyncTask / Thread)?
I ran a search for these topics but I found examples related to UI, ProgressBars and so on .. not really suited/useful to my situation.
read / update data in Firebase
myRefDevices.addValueEventListener(new ValueEventListener() {
// addValueEventListener
// This method is called once with the initial value and again
// whenever data at this location is updated.
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
boolean bChildFound = false;
DatabaseReference dbrefChildFound;
final CDeviceStatus obj_new = new CDeviceStatus();
for( DataSnapshot val : dataSnapshot.getChildren() )
{
if( val.getKey().contentEquals(MAC_ADDRESS[ iIterator ]) )
{
bChildFound = true;
dbrefChildFound = val.getRef();
obj_new.setiAvailable_A( val.getValue( CDeviceStatus.class ).getiAvailable_A() + 1 );
obj_new.setsID(val.getValue( CDeviceStatus.class).getsID() );
dbrefChildFound.setValue(obj_new);
}
}
if(!bChildFound)
{
Log.d("child=" + MAC_ADDRESS[ iIterator ], "not found");
}
if(++iIterator == 16)
{
iIterator = 0;
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
read from file :
try {
// open input stream text file for reading
Resources res = getResources();
InputStream instream = res.openRawResource( R.raw.simulated_bus );
// we convert it to bufferred input stream
BufferedInputStream bistreamSimulatedBus = new BufferedInputStream(instream);
try{
// if we want to stop reading from the file / simulated bus for whatever reason..
boolean bStayInLoop = true;
while ((bistreamSimulatedBus.available() > 0) && bStayInLoop)
{
try {
// throw new InterruptedException();
char c = (char) bistreamSimulatedBus.read();
if( COUNT_CHARACTERS_NEWLINE )
{
if ( '\n' == c ){
// we can count how much NewLine character we have
//iNL_Counter++;
}
}
...
}
catch ( InterruptedException e ) {
throw new RuntimeException( e );
}
}
} catch (IOException e) {
throw new RuntimeException( e );
}
finally {
// release any resource associated with streams
if ( null != instream ) {
instream.close();
}
if ( null != bistreamSimulatedBus ) {
bistreamSimulatedBus.close();
}
}
}
catch (Exception e) {
throw new RuntimeException( e );
}
Thank you.
Let us break the solution like this:
The basics
You have two operations : o1 and o2. You want the second operation to execute as soon as the first one has completed.
It clearly appears to me that you need an event-driven solution here.
Approach
Using the concept of Publisher/Subscriber design pattern, you can make the Initiator of o1 be the Publisher of an event. Then, when this particular operation o1 is completed, let the class (activity, fragment, service) notify the other class which we will call Subscriber.
Code
Add the following line to your build.gradle (app-level):
compile 'org.greenrobot:eventbus:3.0.0'
Then, simply create a simple Plain Old Java Object (POJO) that represents your event.
public class RequestCompletedEvent{ // add constructor and anything you want}
Next, to Publish the event, you simply call the post(POJO instance) like this:
EventBus.getDefault().post(new RequestCompletedEvent(true));
Then, finally, in the Subscriber class, simply listen for notifications by adding the following lines of code:
#Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
#Override
public void onStop() {
super.onStop();
EventBus.getDefault().unregister(this);
}
Then still within the same class, use the Subscribe annotation to catch any signals:
#Subscribe
public void onEvent(RequestCompletedEvent event) {
/* Do something */
//trigger the second operation here;
startOperationTwo();
}
Summary
It would help to note here that the easiest way to pull this off is to use an async task (AsyncTask sub class) to read your files, then when successfully done, inside onPostExecute(), you can notify the Subscriber to initiate the next operation.
I hope this helps; and good luck! Let me know if you need further assistance!
I do a huge polling when user login into my app. At the moment, we don't have much payload. But, the payloads are growing by the 100s daily at the moment. Which makes us have around 300 records per-user. And this, is the first week. So, we started optimizing for the next thousands. API has been well modified, but the Android app is shaky.
I have a request where a user have max of Two Territories for now, and I need to fetch all the Items in each Territory. So, I did this:
/**
* This is to get all user territories and for each and every territory, fetch all items there;
*/
private Observable<ItemListResponse> getAlltemIByTerritory() {
List<String> territories = PrefUtils.getUserTerritories(context);
return Observable.from(territories).flatMap(territory -> fetchAllTerritoryItemPaged(territory, 1));
}
/**
* This is to fetch all items in a passed territory. There's a per_page of 21.
*/
private Observable<ItemListResponse> fetchAllTerritoryItemPaged(String territory, int page) {
Log.e(TAG, "Each territory page: " + page);
return itemAPI.getIems("Bearer " + PrefUtils.getToken(context), page, 21, territory)
.flatMap(itemListResponse -> {
Meta meta = itemListResponse.getBakeResponse().getMeta();
Observable<ItemListResponse> thisPage = Observable.just(itemListResponse);
if (meta.getPage() != meta.getPageCount() && meta.getPageCount() > 0) {
Observable<ItemListResponse> nextPage = fetchAllTerritoryItemPaged(territory, page + 1);
return thisPage.concatWith(nextPage);
} else {
return thisPage;
}
});
}
Utilizing the Observable
public void getAlltemIByTerritory(APIRequestListener apiRequestListener) {
realm.executeTransaction(realm1 -> realm1.where(RealmItem.class).findAll().deleteAllFromRealm());
getAlltemIByTerritory()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<ItemListResponse>() {
#Override
public void onCompleted() {
Log.e(TAG, "Completed Item");
apiRequestListener.didComplete(WhichSync.ITEM);
unsubscribe();
}
#Override
public void onError(Throwable e) {
e.printStackTrace();
handleError(e, apiRequestListener, WhichSync.ITEM);
unsubscribe();
}
#Override
public void onNext(ItemListResponse itemListResponse) {
Log.e(TAG, "In the handler next " + itemListResponse.toString());
realm.executeTransaction(realm1 -> {
for (Item itemData : itemListResponse.getItemResponse().getData()) {
RealmItem realmItem = realm1.where(RealmItem.class).equalTo("id", itemData.getId()).findFirst();
if (realmItem != null)
realmItem.deleteFromRealm();
realm1.copyToRealm(Item.copyBakeryToRealm(itemData));
}
});
apiRequestListener.onProgress(itemListResponse.getItemResponse().getMeta(), itemListResponse.getItemResponse().getData().size(), WhichSync.ITEM);
}
});
}
Log.e(TAG, "Each territory page: " + page); This line and this line Log.e(TAG, "In the handler next " + itemListResponse.toString()); reads to 23 for a 1780 record. With 21 records per_page. And then, the second line stop showing. Then, the view has hang. And then, when the first one is done, I then see the second spitting out logs afterwards.
This works fine, just that when the record is over 700 (The first try was with a 1780 record), there's a huge lag on the UI. And on mostly pre-lollipops it crashes with NoClassDefined Exception and sometimes it works. But, the lag is still always there.
Any help with optimizing the code, thanks.
Tail Recursion here:
/**
* This is to fetch all items in a passed territory. There's a per_page of 21.
*/
private Observable<ItemListResponse> fetchAllTerritoryItemPaged(String territory, int page) {
Log.e(TAG, "Each territory page: " + page);
return itemAPI.getIems("Bearer " + PrefUtils.getToken(context), page, 21, territory)
.flatMap(itemListResponse -> {
Meta meta = itemListResponse.getItemResponse().getMeta();
Observable<ItemListResponse> thisPage = Observable.just(itemListResponse);
if (meta.getPage() != meta.getPageCount() && meta.getPageCount() > 0) {
Observable<ItemListResponse> nextPage = fetchAllTerritoryItemPaged(territory, page + 1);
return thisPage.concatWith(nextPage);
} else {
return thisPage;
}
});
}
is the one causing the problem. Luckily, Rx provides us with BehaviorSubject.
So, I did something like this instead
private Observable<ItemListResponse> fetchAllTerritoryItemPaged(String territory, int page) {
BehaviorSubject<Integer> pageControl = BehaviorSubject.create(page);
Observable<ItemListResponse> ret = pageControl.asObservable().concatMap(integer -> {
if (integer > 0) {
return itemAPI.getIems("Bearer " + PrefUtils.getToken(context), integer, 21, territory)
.doOnNext(itemListResponse -> {
Meta meta = itemListResponse.getItemResponse().getMeta();
if (meta.getPage() != meta.getPageCount() && meta.getPageCount() > 0) {
pageControl.onNext(integer + 1);
} else {
pageControl.onNext(-1);
}
});
} else {
return Observable.<ItemListResponse>empty().doOnCompleted(pageControl::onCompleted);
}
});
return Observable.defer(() -> ret);
}
Which not only remove the lag, but also request comes in pretty fast
I'm trying to get 'Change Subscriptions' to work using the Drive API for Android, but been unsuccessful so far.
Here the simple use case:
2 android devices, both using the same google account
both subscribe to the same 'file of interest' in their drive folder
if the file 'changes', be it from a change performed by one of the two devices or any external source, all devices that subscribed to this file are notified
As far as I understand, this is exactly what 'Change Subscriptions' are supposed to do for me. I'm using play services revision 27.
The problem I have:
A 'file content change' (or some other file event) made locally on one device is never properly propagated to the all other devices that subscribed to the same file.
Does anyone know of any solutions to this issue, or can point my to what I'm doing wrong?
I've written some simple testcode (see below), that only needs a connected googleApiClient, here's what I tested:
1.
device 1 creates a new testfile calling testFileWriteNew() and adds a change subscription to this file using testFileAddAndRemoveSubscription(), the expected log output:
testfile.txt created, driveId=DriveId:CAESABi0AyDAu9XZhVMoAA== resourceId=null
onCompletion; driveId=DriveId:CAESHDBCLXNzaGVuNGlURkFOMGh0ZWtGWU5FeHVTRVUYtAMgwLvV2YVTKAA= resourceId=0B-sshen4iTFAN0htekFYNExuSEU
STATUS_SUCCESS
added subscription to testfile.txt, driveId=DriveId:CAESHDBCLXNzaGVuNGlURkFOMGh0ZWtGWU5FeHVTRVUYtAMgwLvV2YVTKAA= resourceId=0B-sshen4iTFAN0htekFYNExuSEU
2.
device 2 adds a change subscription to the same file using testFileAddAndRemoveSubscription(), the expected log output:
added subscription to testfile.txt, driveId=DriveId:CAESHDBCLXNzaGVuNGlURkFOMGh0ZWtGWU5FeHVTRVUYwgIg9I-GyZRTKAA= resourceId=0B-sshen4iTFAN0htekFYNExuSEU
As expected, the driveId is different on both devices, but the resourceId is the same 0B-sshen4iTFAN0htekFYNExuSEU, so that same 'cloud' file is referenced
3.
If I update the file with some new data via testFileUpdate I get the following on device 1:
testfile.txt updated, driveId=DriveId:CAESHDBCLXNzaGVuNGlURkFOMGh0ZWtGWU5FeHVTRVUYtAMgwLvV2YVTKAA= resourceId=0B-sshen4iTFAN0htekFYNExuSEU
and device 2:
testfile.txt updated, driveId=DriveId:CAESHDBCLXNzaGVuNGlURkFOMGh0ZWtGWU5FeHVTRVUYwgIg9I-GyZRTKAA= resourceId=0B-sshen4iTFAN0htekFYNExuSEU
4.
Unfortunately, the 'change of content' in the onChange method of the service is only triggered locally. A changed done by device 1 never reaches device 2 and vice versa. If I update the file using device 2 I see the following log on device 2 coming from the service:
onChange; driveId=DriveId:CAESHDBCLXNzaGVuNGlURkFOMGh0ZWtGWU5FeHVTRVUYwgIg9I-GyZRTKAA= resourceId=0B-sshen4iTFAN0htekFYNExuSEU
contentChanged
onChange; driveId=DriveId:CAESHDBCLXNzaGVuNGlURkFOMGh0ZWtGWU5FeHVTRVUYwgIg9I-GyZRTKAA= resourceId=0B-sshen4iTFAN0htekFYNExuSEU
metadataChanged
but I never see the onChange method being triggered on device 1, if device 2 triggered a change, which I would expect.
Code:
private boolean testFileWriteNew() {
final DriveFolder folderRoot = Drive.DriveApi.getRootFolder(mGoogleApiClient);
DriveContentsResult contentsResult = Drive.DriveApi.newDriveContents(mGoogleApiClient).await();
if (!contentsResult.getStatus().isSuccess()) {
return false;
}
DriveContents originalContents = contentsResult.getDriveContents();
OutputStream os = originalContents.getOutputStream();
try {
os.write(String.valueOf(System.currentTimeMillis()).getBytes());
MetadataChangeSet originalMetadata = new MetadataChangeSet.Builder().setTitle("testfile.txt").setMimeType("text/plain").build();
// create the file in root
DriveFolder.DriveFileResult fileResult = folderRoot.createFile(mGoogleApiClient, originalMetadata, originalContents, new ExecutionOptions.Builder().setNotifyOnCompletion(true).build()).await();
if (!fileResult.getStatus().isSuccess()) {
return false;
}
// check 'locally created' file, not yet synced to drive
DriveResource.MetadataResult metadataResult = fileResult.getDriveFile().getMetadata(mGoogleApiClient).await();
if (!metadataResult.getStatus().isSuccess()) {
return false;
}
Log.d(TAG, "testfile.txt created, driveId=" + metadataResult.getMetadata().getDriveId().encodeToString() + " resourceId=" + metadataResult.getMetadata().getDriveId().getResourceId());
return true;
} catch (IOException ioe) {
return false;
}
}
private boolean testFileUpdate() {
final DriveFolder folderRoot = Drive.DriveApi.getRootFolder(mGoogleApiClient);
// find testfile
DriveId testFile = null;
MetadataBufferResult folderFilesSyncFolder = folderRoot.listChildren(mGoogleApiClient).await();
if (!folderFilesSyncFolder.getStatus().isSuccess()) {
return false;
} else {
MetadataBuffer bufferMetaData = folderFilesSyncFolder.getMetadataBuffer();
for(int i = 0; i < bufferMetaData.getCount(); ++i) {
final Metadata data = bufferMetaData.get(i);
if(!data.isFolder() && !data.isTrashed() && data.isEditable() && data.getTitle().equalsIgnoreCase("testfile.txt")) {
testFile = data.getDriveId();
break;
}
}
bufferMetaData.release();
}
if(testFile == null) {
return false;
}
// update testfile
DriveFile file = Drive.DriveApi.getFile(mGoogleApiClient, testFile);
DriveContentsResult driveContentsResult = file.open(mGoogleApiClient, DriveFile.MODE_WRITE_ONLY, null).await();
if (!driveContentsResult.getStatus().isSuccess()) {
return false;
}
DriveContents originalContents = driveContentsResult.getDriveContents();
OutputStream os = originalContents.getOutputStream();
try {
os.write(String.valueOf(System.currentTimeMillis()).getBytes());
// commit changes
com.google.android.gms.common.api.Status status = originalContents.commit(mGoogleApiClient, null).await();
if(!status.isSuccess()) {
return false;
}
Log.d(TAG, "testfile.txt updated, driveId=" + file.getDriveId().encodeToString() + " resourceId=" + file.getDriveId().getResourceId());
return true;
} catch (IOException ioe) {
return false;
}
}
private boolean testFileAddAndRemoveSubscription(boolean subscribe) {
final DriveFolder folderRoot = Drive.DriveApi.getRootFolder(mGoogleApiClient);
// find testfile
DriveId testFile = null;
MetadataBufferResult folderFilesSyncFolder = folderRoot.listChildren(mGoogleApiClient).await();
if (!folderFilesSyncFolder.getStatus().isSuccess()) {
return false;
} else {
MetadataBuffer bufferMetaData = folderFilesSyncFolder.getMetadataBuffer();
for(int i = 0; i < bufferMetaData.getCount(); ++i) {
final Metadata data = bufferMetaData.get(i);
if(!data.isFolder() && !data.isTrashed() && data.isEditable() && data.getTitle().equalsIgnoreCase("testfile.txt")) {
testFile = data.getDriveId();
break;
}
}
bufferMetaData.release();
}
if(testFile == null) {
return false;
}
// subscribe & unsubscribe
DriveFile file = Drive.DriveApi.getFile(mGoogleApiClient, testFile);
if(subscribe) {
com.google.android.gms.common.api.Status status = file.addChangeSubscription(mGoogleApiClient).await();
if(!status.isSuccess()) {
return false;
}
Log.d(TAG, "added subscription to testfile.txt, driveId=" + file.getDriveId().encodeToString() + " resourceId=" + file.getDriveId().getResourceId());
return true;
} else {
com.google.android.gms.common.api.Status status = file.removeChangeSubscription(mGoogleApiClient).await();
if(!status.isSuccess()) {
return false;
}
Log.d(TAG, "removed subscription from testfile.txt, driveId=" + file.getDriveId().encodeToString() + " resourceId=" + file.getDriveId().getResourceId());
return true;
}
}
And here the service class:
public class ChangeService extends DriveEventService {
// TAG
private static final String TAG = ChangeService.class.getSimpleName();
#Override
public void onChange(ChangeEvent event) {
final DriveId driveId = event.getDriveId();
Log.e(TAG, "onChange; driveId=" + driveId.encodeToString() + " resourceId=" + driveId.getResourceId());
if(event.hasContentChanged()) { Log.e(TAG, "contentChanged"); }
else if(event.hasMetadataChanged()) { Log.e(TAG, "metadataChanged"); }
else if(event.hasBeenDeleted()) { Log.e(TAG, "beenDeleted"); }
}
#Override
public void onCompletion(CompletionEvent event) {
final DriveId driveId = event.getDriveId();
Log.e(TAG, "onCompletion; driveId=" + driveId.encodeToString() + " resourceId=" + driveId.getResourceId());
switch (event.getStatus()) {
case CompletionEvent.STATUS_CONFLICT: Log.e(TAG, "STATUS_CONFLICT"); break;
case CompletionEvent.STATUS_FAILURE: Log.e(TAG, "STATUS_FAILURE"); break;
case CompletionEvent.STATUS_SUCCESS: Log.e(TAG, "STATUS_SUCCESS "); break;
case CompletionEvent.STATUS_CANCELED: Log.e(TAG, "STATUS_CANCELED "); break;
}
event.dismiss();
}
}
I believe, you are falling into the same trap as many of us did before. I too originally assumed that the 'DriveEventService' takes care of notifications between multiple devices running under the same account. I tried and failed miserably, see here (and notice the resounding silence - since April 2014). I was always getting events on a single device only. So, I actually realized that Change Events work only locally within the GooPlaySvcs instance.
This was more or less confirmed by a comment from Steve Bazyl in this unrelated answer (please read including the 'ORIGINAL POST' paragraph), confirming my theory that both 'Change Events' and 'Completion Events' are local (Completion Events report result of network action - like http response).
So to answer your question. after fighting this for awhile, I had to develop a different strategy:
1/ perform GDAA action (create, update)
2/ wait for a Completion Event indicating your mod has been promoted to the Drive
3/ broadcast GCM message that include ResourceId (not DriveId !) plus optional data (up to 4K) to the registered participants.
4/ 'Registered participants' react to the message and download updated metadata/content, resolving the conflicts.
This solution is from summer 2014 and there may be some other pre-packaged solutions from Google since. I'd be happy myself to hear from people who know if there is more elegant solution.
Quite frankly, I don't understand what is this and this for, if the Completion Events do not timely reflect (notify of) the update from another device.
Good Luck
I am using parse for my data in android app. for some reason the code inside query.findInBackground is not getting executed.
public List<Date> sessionHeaderFetch(){
Log.d("test", "session fetch entry");
ParseQuery<Sessions> query = ParseQuery.getQuery(Sessions.class);
final List<Date> sessionHeaders = null;
query.findInBackground(new FindCallback<Sessions>() {
#Override
public void done(List<Sessions> sessionsObjects, com.parse.ParseException e) {
Log.d("test", "session internal entry");
if (e == null) {
Log.d("test", "Retrieved " + sessionsObjects.size() + " sessions");
for (Sessions session : sessionsObjects) {
sessionHeaders.add(session.getNetsDate());
};
} else {
Log.d("score", "Error: " + e.getMessage());
}
}
});
Log.d("test", "session last value");
return sessionHeaders;
}
the code inside public void done() is not all invoked.
I don't think you have understood how to query correctly in Parse.
When you define the parse query. The get query should contain the name of the table that you are trying to query. Also the queries returned will be ParseObjects normally, so I would expect that your callback should be new FindCallback().
I've adjusted the parse query below.
ParseQuery<Sessions> query = ParseQuery.getQuery("ParseTableName");
final List<Date> sessionHeaders = null;
query.findInBackground(new FindCallback<ParseObject>() {
#Override
public void done(List<ParseObject> sessionsObjects, com.parse.ParseException e) {
Log.d("test", "session internal entry");
if (e == null) {
// Find succeeded, so do something
} else {
Log.d("score", "Error: " + e.getMessage());
}
}
});
Obviously you will need to replace "ParseTableName" with the name of the table that you are trying to query in parse.
I actually solved it by using an ArrayList, there were some problems with the List initialization and also the results are fetched asynchronous manner so my functions which calls these functions were getting null values so I had to write by calling functions that calls the parse fetch functions asynchronously as well.
but the query can be written like this and it works.
public void sessionFetch(Date headers, final ParseIOListener<Sessions> listener){
ParseQuery<Sessions> query = ParseQuery.getQuery(Sessions.class);
Log.d("test", "input header" + headers );
query.whereEqualTo("NetsDate",headers);
final ArrayList<Sessions> sessionsData = new ArrayList<Sessions>();
query.findInBackground(new FindCallback<Sessions>() {
#Override
public void done(List<Sessions> sessionsObjects, com.parse.ParseException e) {
if (e == null) {
for (Sessions session : sessionsObjects) {
Log.d("test", "output objects" + session.toString() );
sessionsData.add(session);
Log.d("test", "Retrieved -- sessions" + sessionsObjects.size() );
}
listener.onDataRetrieved(sessionsData);
} else {
listener.onDataRetrieveFail(e);
}
}
});
}
If you want more details on implementing this, check this link
The reason why you feel the code is not being invoked is because you are returning from the method before the ".findInBackground" operation has completed. Remember the query is performed on a background thread. So this is how your code will run:
ParseQuery query = ParseQuery.getQuery(Sessions.class);
final List sessionHeaders = null;
------> 1. query.findInBackground (Popped onto background thread)
return sessionHeaders; (by the time you get here, sessionHeaders will probably still be null).
So change the logic of the code and wait till the callback returns before doing any processing on the "sessionHeaders" object.
I'm trying to indicate the authentication / sync status of an account using the AccountAuthenticator and SyncAdapter. I've been through the samples, and can get it working alright.
How can I set the indicator to red just like the GMail account?
I'd also like to add additional status indicators on the sync adapter page. See picture below:
Answering my own question for future team knowledge...
Getting the indicator to change color was fairly easy after some experimentation. Start by creating a project based on thecode supplied in the SDK sample projects, modify as follows:
1) Fake the initial login from the server during the AuthenticationActivity. Once past the initial check, the system will start it's periodic sync attempts.
/**
* Called when the authentication process completes (see attemptLogin()).
*/
public void onAuthenticationResult(boolean result) {
Log.i(TAG, "onAuthenticationResult(" + result + ")");
// Hide the progress dialog
hideProgress();
// Override the result, we don't care right now....
result = true;
if (result) {
if (!mConfirmCredentials) {
finishLogin();
} else {
finishConfirmCredentials(true);
}
} else {
Log.e(TAG, "onAuthenticationResult: failed to authenticate");
if (mRequestNewAccount) {
// "Please enter a valid username/password.
mMessage.setText(getText(R.string.login_activity_loginfail_text_both));
} else {
// "Please enter a valid password." (Used when the
// account is already in the database but the password
// doesn't work.)
mMessage.setText(getText(R.string.login_activity_loginfail_text_pwonly));
}
}
}
2) Modify the "onPerformSync()" method within the SyncAdapter. The key here are the "syncResult.stats" fields. While modifying them, I found that inserting multiple errors didn't get the effect I wanted. Also noting that the counts didn't seem to be recorded across sync attempts (i.e. the fails always come in as zero). The "lifetimeSyncs" is a static variable that keeps count across sync attempts. This modified code will continue to alternate between green and red...
#Override
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
List<User> users;
List<Status> statuses;
String authtoken = null;
try {
// use the account manager to request the credentials
authtoken = mAccountManager.blockingGetAuthToken(account, Constants.AUTHTOKEN_TYPE, true );
// fetch updates from the sample service over the cloud
//users = NetworkUtilities.fetchFriendUpdates(account, authtoken, mLastUpdated);
// update the last synced date.
mLastUpdated = new Date();
// update platform contacts.
Log.d(TAG, "Calling contactManager's sync contacts");
//ContactManager.syncContacts(mContext, account.name, users);
// fetch and update status messages for all the synced users.
//statuses = NetworkUtilities.fetchFriendStatuses(account, authtoken);
//ContactManager.insertStatuses(mContext, account.name, statuses);
if (SyncAdapter.lifetimeSyncs-- <= 0 ){
//mAccountManager.invalidateAuthToken(Constants.ACCOUNT_TYPE, authtoken);
syncResult.stats.numAuthExceptions++;
//syncResult.delayUntil = 60;
lifetimeSyncs = 5;
}
} catch (final AuthenticatorException e) {
syncResult.stats.numParseExceptions++;
Log.e(TAG, "AuthenticatorException", e);
} catch (final OperationCanceledException e) {
Log.e(TAG, "OperationCanceledExcetpion", e);
} catch (final IOException e) {
Log.e(TAG, "IOException", e);
Log.d(TAG, extras.toString());
syncResult.stats.numAuthExceptions++;
syncResult.delayUntil = 60;
//extras.putString(AccountManager.KEY_AUTH_FAILED_MESSAGE, "You're not registered");
} catch (final ParseException e) {
syncResult.stats.numParseExceptions++;
Log.e(TAG, "ParseException", e);
}
}
That's it, enjoy playing with the delays and other variables too...