0

Some preliminary information:

I have to create an API for an Android Library. This API has to deal with basic CRUD (find, find all, insert, update, delete) operations of generic data. Then other apps that use this library can use this API with any data/object they want. However, this data is stored in a backend server, where it's stored and used by all users that have the same app in different devices (basically a BaaS). The apps should be able to work without an internet connection. So checked out a video Google I/O made about how to design REST clients in android and followed it.

Thus now:

  • When inserting/updating/deleting data, the API uses a local Content Provider and sets specific flags to the data.
  • I have a SyncAdapter running, which checks all data in the sqlite in each run.
  • It checks the flag the data has (if it has been inserted, updated or deleted), and calls a REST API to sincronize it with the server

However, I want the apps to be able to pass a callback to this sincronization. Basically, what I want is, when the app calls the "insert" method from my API, it should pass it a callback. This callback should do 2 things:

  • Define what to do in case the insert failed
  • Define what to do in case the insert succeeded.

This is an example of what I'd want to do:

GenericData data = new GenericData();
//Initialize data
API.insert(GenericData.class, data, new CallBack<GenericData>(){

    @Override
    public void onSuccess(GenericData insertedData){
        //Data inserted and syncronized successfully
    }

    @Override
    public void onFailure(){
        //Data failed to be inserted an/or sincronized
    }
});

This callback, as an annonymous class, would be called from an AsyncTask in the app. What I want to do, is call callBack.onSuccess(data) from the SyncAdapter if the sync is done with success, or call callBack.onFailure() from the SyncAdapter if the sync failed.

The Callback class would thus look something like this:

//Concrete empty methods are set so it's not necessary to implement all these methods
public abstract class Callback<T>{
    public void onSuccess(T insertedData){}
    public void onFailure(){}
}

What I had in mind was this:

Create a BroadcastReceiver that will handle the "insert" callbacks. This receiver will be listening for specific Intents.

When the SyncAdapter finished syncing a row of inserted data, it will create a specific Intent and broadcast it. This Intent would have, as additional data, the id of the row processed, and the status of the sync (if it succeeded or if it failed). When the receiver is called, it gets the id of the row from the intent, gets the specific callback for that id, and depending on the status of the sync calls callBack.onSuccess(data) or callBack.onFailure() .

My problem comes from determining which callback to call. Each "API.insert(..)" method is passed a Callback subclass, which is an annonymous class. I don't know how to serialize it, or "store" it with the specific id of the row. If it could be serialized somehow, then the BroadcastReceiver would just do Callback callback = lookup(id) and get the callback associated to that id. However I don't know if this is possible, because I tried serializing the callback and it didn't work. I think the problem is that the Activity itself (where the insert method is called from) is not serializable itself, so the callback can't be serialized either.

I thought about maybe not using an annonymous class, but use a named Callback class instead. But then again, if I allow to pass a Callback object in the method, then you can still pass an annonymous class to it. Thus any app that does exactly that will have errors. So the callbacks should work even if they are passed as annonymous classes, unless there is an alternative for that situation above.

Also, I'd like it if it was an annonymous class inside the Activity, so the callback can use variables defined in the activity. For example, to insert 2 different objects in a row (the callback would use a OtherData data object defined in the activity). If it can somehow be done, then if the Activity is still up and running, the callback should use that same Activity object. But if the Activity is closed, somehow serialize it, so when the callback is called later it uses the variables/data from the Activity right before it was cloaed/destroyed

Any ideas?

P.S: Also, like I said the callbacks and the data should be generic, so the method for calling the callback should support any possible type. I guess this won't be a problem anyways, for example using a wildcard Callback<?> callback = lookupCallback(id).


Update:

Okay, I think I have a solution, perhaps.

Then main problem I had, was that I needed the Callback to be an annonymous class and serializable at the same time, and that is not possible. If it is serializable, but not an annoynmous class, I can't use variables/attributes from the Activity that calls it (which is necessary to process those callbacks). But if it is an annonymous class, but not serializable, then I can't serialize the callback for it to be deserialized and called when the SyncAdapter finishes syncing.

So I thought maybe I could do it like this, to include the best of both worlds:

This would be the CallBack class:

//Concrete empty methods are set so it's not necessary to implement all these methods
public abstract class Callback<T> implements Serializable{
    public void onSuccess(T insertedData){}
    public void onFailure(){}
}

Every callback should be a Named class (external class, or static inner class, etc), and in its creator pass an activity. Then you can have any field you want taken from that activity that you want.

I think this would be better shown with examples, so I'll try including one: "I create a User and an Address that has that user. I want to insert the User, then insert the address when I know that user was inserted".

In this case, I think I'd have a callback like this:

public class UserInsertedCallback extends Callback<User> implements Serializable{
    //Here goes serialId
    private Address address;

    public UserInsertedCallback(UserActivity activity){
        address = activity.getAddress();
    }

    @Override
    public void onSuccess(User insertedUser){
        //This is another callback we may want to use
        Callback<Address> callback = createCallback();
        //I create Foreign Key in Address referencing the user
        address.setUserId(insertedUser.getId());
        API.insert(Address.class, address, callback); 
    }
}

Now this would be the activity:

public class UserActivity extends Activity{
    private Address address;
    ....
    public Address getAddress(){return address;}

    private class TestTask extends AsyncTask<Void,Void,Void>{
       @Override
       protected Void doInBackground(Void... void){
            User user = ... //create user
            address = ... //create address
            API.insert(User.class, user, new UserInsertedCallback(UserActivity.this));
        }
    }
}

My API method would serialize UserInsertedCallback and insert it into a database table that can be easily retrieved. There is no problem here, assuming Address is serializable. Even if it's not, the developer would just include serializable objects/primitives in UserInsertedCallback and could create the Address object again in onSuccess(). With the callback now serialized, whenever the SyncAdapter finishes inserting the User successfully, it can obtain the serialized callback from the database, deserialize it, and call "onSuccess(insertedUser)" from it.

If the Address object is the only thing needed, then the constructor of UserInsertedCallback could have taken it instead. But perhaps the developer would need other stuff from the Activity as well, so they have the ability to pass the activity as well.

Granted, I'm sure it's not as easy as that, but I will soon try to see if this works. I still don't know how to prevent people from passing the callback as an annonymous class though

gonzaw
  • 771
  • 4
  • 17
  • For reference, I want to do something similar to this: http://devcenter.kinvey.com/android/guides/datastore – gonzaw Nov 08 '13 at 18:32

2 Answers2

1

So I was finally able to get this going on.
It worked like I put it in my Edit, but I'll give some more explanations:

Basically, the Callback class is similar to how I defined it above, except I changed the methods a little bit:

public abstract class Callback<T> implements Serializable{
    private static final long serialVersionID = ...; //version Id goes here
    public void onSuccess(T insertedData){}
    public void onFailure(T failedData, CustomException ex){}
}

I pass more info to the onFailure event, such as the data that failed the synchronization, and a custom exception thrown by the synchronization (since the synchronization can fail for a lot of reasons).

Then each app can create a named class that extends Callback<T>, and can have any field it wants (as long as it's serializable), just like I mentioned in the Edit. My API call takes this callback as a parameter, serializes it, and stores it in the database alongside with the _ID of the data to synchronize.
Later on, my SyncAdapter takes that data and tries to synchronize it with the server. If a fatal error occurs (one it can't recover from and try again later for instance), it reverts the data back to its original state and sends a broadcast message passing the serialized data, a serialized exception (both via JSON), the _ID field, and passing a parameter stating the synchronization failed.
Then I set up a BroadcastReceiver who listens for this broadcast, gets all this information and starts a Service with the same extras.
This new service gets the _ID field, looks up the serialized callback from the database, deserializes it, deserializes the data and the custom exception, and calls callback.onFailure(data, ex), and it works pretty well!
Once the callback is finished being called, it is deleted from the database. All of this (except defining the extended callback class and calling the API) is done in the Android library itself.

P.S: Actually, the data is in JSON format and de/serialized with GSON. To do this I added a Class<T> data_class; field to Callback<T>, and a onFailureFromJson(String json, CustomException ex) final method which deserializes the JSON into an object of type T, and calls onFailure(entity,ex) with it. Thus the service calls this onFailureFromJson method instead.
Here's the final Callback<T> class:

public abstract class Callback<T> implements Serializable{
    private static final long serialVersionID = ...; //version Id goes here
    private Class<T> data_class;

    public Callback(Class<T> data_class){this.data_class = data_class;}
    public abstract void onSuccess(T insertedData){}
    public abstract void onFailure(T failedData, CustomException ex){}

    public final void onSuccessFromJson(String json){
        Gson gson = new GsonBuilder().create();
        T entity = gson.fromJson(json,data_class);
        onSuccess(entity);
    }
    public final void onFailureFromJson(String json, CustonException ex){
        Gson gson = new GsonBuilder().create();
        T entity = gson.fromJson(json,data_class);
        onFailure(entity,ex);    
    }    
}
gonzaw
  • 771
  • 4
  • 17
  • Please try defining your methods `abstract` as well: `public abstract void onSuccess(T insertedData);` and `public abstract void onFailure(T failedData, CustomException ex);` – JJD Feb 05 '14 at 00:32
0

although there might be better ways i'm not aware of, a working technique

public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {

   //do your CURD actions
   boolean isSuccess = false; //according to your operation action set this to true or false

   if(isSuccess){
        new ExtendedCallback.onSuccess(); 
        //ExtendedCallback is the extension of Callback class to suit your requirement
        //of course yu would have to define this new class class and send it to you SyncAdapter class before using
   }
}
ManZzup
  • 526
  • 4
  • 12
  • Hi, thanks for answering. I'm not sure I get it, where is ExtendedCallback defined? In the API, or in the app that uses the API?. – gonzaw Nov 08 '13 at 17:49
  • no extendedCall back is the extended class of your Callback class ex: [im ignoring the genrics here for the sake of simplicity] `class ExtendedCallback extends Callback{ public void onSuccess(T insertedData){ //do custom onSuccess action } public void onFailure(){ //do custom onFailure action } }` – ManZzup Nov 08 '13 at 17:52
  • These "extendedCallbacks" should be defined in the app. The API itself has no knowledge of any subclass of Callback, since it's just a library any app can use. So the API itself can't call `ExtendedCallback.onSuccess();` since it won't be defined. The SyncAdapter is in the API by the way (although the configuration is done in the AndroidManifest.xml of the specific app) – gonzaw Nov 08 '13 at 18:12
  • yeah the extendedCallback can easily be sent as an anonymous class argument to a proper contructor ex: `public CustomSyncAdapter(Context context, boolean autoInitialize, Callback c){ //bla bla }` and we initialize as `new CustomSyncAdapter(,,new Callback(){ //abstract implementations })` – ManZzup Nov 08 '13 at 18:17
  • Hmm, but isn't the SyncAdapter created in the SyncService? How can I pass the specific callback to it? – gonzaw Nov 08 '13 at 18:21
  • we extend the `AbstractThreadedSyncAdapter` class to make our own `SyncAdapter` ref: [android doc](http://developer.android.com/training/sync-adapters/creating-sync-adapter.html) – ManZzup Nov 08 '13 at 18:23
  • But that would mean every app would have to extend their own SyncAdapter? And do that for every callback they make? I already have a SyncAdapter in the API that does the necessary calls and logic to syncronize everything, apps can't use a different SyncAdapter (or rather, shouldn't since all the sync is done in the one defined in the API) – gonzaw Nov 08 '13 at 18:34
  • since you said that your class should be generic, to be able to use with all CURD operations i suggested to make a generic SyncAdapter if you already have one simply extends the available SyncAdapter to add the required additional feature ie: the Callback class ex: `class CustomSyncAdapter extends SyncAdapter{ public CustomSyncAdapter(Context context, boolean autoInitialize, Callback c){ super(context,autoInitialize); //bla bla } }` – ManZzup Nov 08 '13 at 18:38
  • Sorry, but I still don't understand. What would be the lifecycle of the callback on your model? From where the app calls "insert(...,callback)", to when its `onSuccess` or its `onFailure` methods are executed – gonzaw Nov 08 '13 at 22:18
  • for a better clarification i need the structure of your api and insert commands, ill put a pastebin link to the model i propose – ManZzup Nov 09 '13 at 03:43
  • Sure, what do you want to know? But it doesn't do much from what I said already. The "insert" method converts the data to JSON and inserts it into the Content Provider. The SyncAdapter is regularly called, checks the inserted data and syncronizes it with the server (sends the JSON data via REST) – gonzaw Nov 09 '13 at 04:11
  • what i want to know is how exactly the API work inbetween SyncAdapter and insert method, specific line where this interaction occurs so that we can try to either extend the existing API, btw is the callback is taken as a parameter in the API? – ManZzup Nov 09 '13 at 05:04
  • Yes, the callback would be taken as a paremeter in the API. Like I said, there is no other interaction other than this: 1)The insert method, inserts a specific row into the SQLite database: "". The SyncAdapter regularly checks the database via the Content Provider, and says "okay, this row has the to it, so it was inserted previously, I'll sincronize it with the server". So it sends an REST message to an "insert" method in the backend, which inserts the data in the server. After that, it removes the from the row in the db. – gonzaw Nov 09 '13 at 15:59
  • are you free to edit the source of the API or is it an external one? because i think if it even accept the callback, editing it would be a lot more easier than creating a new one – ManZzup Nov 09 '13 at 16:56
  • Yes. I'm the one creating the API to support these callbacks – gonzaw Nov 09 '13 at 17:11
  • alryt then i think its simpler than i thought, you got all the SyncAdapter parts done only need to have a generic callback method? then simply pass an anonymous extension of the callback class as the parameter to the API call – ManZzup Nov 10 '13 at 03:41
  • Yes, that's what I want to do. The problem is that the API (the "insert" method) and the SyncAdapter are running in different processes, and the SyncAdapter may run way after the API's lifecyle is over (i.e when the app has closed). The callback should still be called in that case, but since they are on different lifecycles I can't just pass it around from one to the other – gonzaw Nov 10 '13 at 08:50
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/40924/discussion-between-manzzup-and-gonzaw) – ManZzup Nov 11 '13 at 02:19