Passer au contenu principal

 Subscribe

We’ve released a major update in the Android SDK for Azure Mobile Services. Addressing some feedback about the usage of asynchronous calls in Android, we added support for Futures in all of the asynchronous operations so that you can easily perform multiple of those operations (while on a background thread) without having to deal with multiple nested callbacks. The changes are additive (new methods which return futures in side by side with the ones which take a callback parameter) in most of the scenarios, but in some fundamental interfaces used in more advanced ones (such as the ServiceFilter) we made a breaking change so that futures is now the default model used by the Mobile Services Android SDK for dealing with asynchronous operations. In this release we also added offline support, but to focus on the breaking changes I’ll talk about that in an upcoming post.

TL;DR: the rest of this post talks about the new futures support and the list of breaking changes; if all you want is the new SDK to play with it, you can get it at https://aka.ms/Iajk6q. Notice that it’s still an alpha version and subject to change.

Futures

Here’s an example of a code which is made simpler by the usage of the new futures support. Before, if we needed to execute multiple operations, the nesting of the callbacks could quickly become a burden. For example, if you need to iterate through the result of a query and update the items, the callback-based code can quickly become non-trivial:

final MobileServiceTable table = mClient.getTable(TodoItem.class);
table.where().field("complete").eq(false).execute(new TableQueryCallback() {

    @Override
    public void onCompleted(final List items, int unused,
            Exception error, ServiceFilterResponse response) {

        TableOperationCallback updateCallback = new TableOperationCallback() {

            private int mIndex;

            @Override
            public void onCompleted(TodoItem updated,
                    Exception error, ServiceFilterResponse response) {
                mIndex++;
                if (mIndex == items.size()) {
                    tv.setText("Marked all items as complete");
                } else {
                    TodoItem item = items.get(mIndex);
                    item.setComplete(true);
                    table.update(item, this);
                }

            }
        };

        if (items.size() > 0) {
            TodoItem first = items.get(0);
            first.setComplete(true);
            table.update(first, updateCallback);
        }

    }
});

With the futures support, if the code is running a background thread, the calls are much cleaner. Take the code below, which does the same thing as the one above. It’s boils down to 6 lines of code (with some exception handling and thread skipping sprinkled, which would not be necessary if the code was already not in the UI thread, or if it didn’t need to modify a UI component).

new AsyncTask() {

    @Override
    protected Void doInBackground(Void... params) {
        final MobileServiceTable table = mClient.getTable(TodoItem.class);
        try {
            MobileServiceList results = table.where().field("complete").eq(false).execute().get();
            for (TodoItem todoItem : results) {
                todoItem.setComplete(true);
                table.update(todoItem).get();
            }
            runOnUiThread(new Runnable() {

                @Override
                public void run() {
                    tv.setText("Marked all items as complete");
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

}.execute();

Notice that the code above is smaller, because it has some exception handling and thread skipping sprinkled around. But the code logic boils down to the following 6 lines of code:

final MobileServiceTable table = mClient.getTable(TodoItem.class);
MobileServiceList results = table.where().field("complete").eq(false).execute().get();
for (TodoItem todoItem : results) {
    todoItem.setComplete(true);
    table.update(todoItem).get();
}

That’s the main advantage of using futures: simple programming model (especially regarding chaining futures). But it also gives you other features, such as cancellation, ease of merging (join) multiple futures, among others.

New APIs

Ok, futures are great, how to I use it? For the main operations – table and custom APIs, each method that previously took a callback parameter has a new overload – which doesn’t take the callback but returns a future interface instead. To minimize the changes needed for existing apps the current callback-based methods are still there, but they’re now marked as @deprecated so that they may be removed in a future release (with a major version upgrade). This applies to custom APIs (e.g., before there were 9 overloads of the invokeApi method of the MobileServiceClient class, there are now 18), push registration / unregistration, typed and untyped tables (all CRUD operations) and login (all overloads). For example, those are the old overloads of the invokeApi method:

public  void invokeApi(String apiName, Class clazz, ApiOperationCallback callback)
public  void invokeApi(String apiName, Object body, Class clazz, ApiOperationCallback callback)
public  void invokeApi(String apiName, String httpMethod, List> parameters, Class clazz, ApiOperationCallback callback)
public  void invokeApi(String apiName, Object body, String httpMethod, List> parameters, final Class clazz, final ApiOperationCallback callback)
public void invokeApi(String apiName, ApiJsonOperationCallback callback)
public void invokeApi(String apiName, JsonElement body, ApiJsonOperationCallback callback)
public void invokeApi(String apiName, String httpMethod, List> parameters, ApiJsonOperationCallback callback)
public void invokeApi(String apiName, JsonElement body, String httpMethod, List> parameters, final ApiJsonOperationCallback callback)
public void invokeApi(String apiName, byte[] content, String httpMethod, List> requestHeaders, List> parameters, final ServiceFilterResponseCallback callback)

And those are the new versions, which exist side-by-side with the existing ones:

public  ListenableFuture invokeApi(String apiName, Class clazz)
public  ListenableFuture invokeApi(String apiName, Object body, Class clazz)
public  ListenableFuture invokeApi(String apiName, String httpMethod, List> parameters, Class clazz)
public  ListenableFuture invokeApi(String apiName, Object body, String httpMethod, List> parameters, final Class clazz)
public ListenableFuture invokeApi(String apiName)
public ListenableFuture invokeApi(String apiName, JsonElement body)
public ListenableFuture invokeApi(String apiName, String httpMethod, List> parameters)
public ListenableFuture invokeApi(String apiName, JsonElement body, String httpMethod, List> parameters)
public ListenableFuture invokeApi(String apiName, byte[] content, String httpMethod, List> requestHeaders, List> parameters)

Breaking changes

When moving to the futures-only / callback-less model, we could maintain backward compatibility in major classes by deprecating the existing methods and adding new methods with the Futures result – and that’s what we did. There are some interfaces which were callback-based, however, and we couldn’t add the futures methods to that interface (as it would break any class which implemented it). We could have duplicated all of the interfaces and made the class implement both the new and the old ones, but that would have caused an unnecessary bloat in the packages. And since we’re moving on the direction of the futures-based code, we decided to go ahead and introduce a breaking change for those interfaces, which is why the new release contains a major version increase. And since we’ve crossed the high threshold for breaking changes, we used the opportunity to do some cleanup in our code, including splitting the (rather large) com.microsoft.windowsazure.mobileservices package into multiple “sub-packages” so that the classes are better organized. Finally, we also changed the query result so that there’s less code to be written. Let’s walk through all breaking changes, and how they can be addressed in your code.

Package changes

This is the list of public classes which belonged to the com.microsoft.windowsazure.mobileservices package, and now belong to new packages. Those marked with (*) have additional changes listed below.

  • com.microsoft.windowsazure.mobileservices.authentication
    • MobileServiceAuthenticationProvider
    • MobileServiceUser
  • com.microsoft.windowsazure.mobileservices.http
    • AndroidHttpClientFactory
    • AndroidHttpClientFactoryImpl
    • NextServiceFilterCallback (*)
    • ServiceFilter (*)
    • ServiceFilterRequest
    • ServiceFilterResponse
  • com.microsoft.windowsazure.mobileservices.notifications
    • GcmNativeRegistration
    • GcmTemplateRegistration
    • MobileServicePush
    • Registration
    • RegistrationCallback
    • RegistrationGoneException
    • TemplateRegistration
    • TemplateRegistrationCallback
    • UnregisterCallback
  • com.microsoft.windowsazure.mobileservices.table
    • MobileServiceJsonTable
    • MobileServicePreconditionFailedException
    • MobileServicePreconditionFailedExceptionBase
    • MobileServiceSystemProperty
    • MobileServiceTable
    • TableDeleteCallback
    • TableJsonOperationCallback
    • TableJsonQueryCallback
    • TableOperationCallback
    • TableQueryCallback
  • com.microsoft.windowsazure.mobileservices.table.query
    • MobileServiceQuery (*)
      • Renamed to Query, with two new subclasses: ExecutableQuery and ExecutableJsonQuery
    • MobileServiceQueryOperations
      • Class with the static operations used for creating typed queries. Renamed to QueryOperations.
    • QueryOrder

There were lots of package changes, but they can be addressed automatically in Eclipse: on the “Source” menu, select “Organize Imports” (or Ctrl+Shift+O)

MobileServiceQuery

The MobileServiceQuery interface was used to represent a query to a table, and it was a generic class typed to the parameter ‘E’. But it was used for both typed (serialization) and untyped (JSON) scenarios, which went against the pattern used in the rest of the SDK. The type of the query object is now ExecutableQuery for the typed case, and ExecutableJsonQuery for the untyped one. Notice that if you don’t store the query object in a variable, the code doesn’t need to be updated. Take the code below which works on the existing (1.1.6) version of the SDK:

public class QueryChangesDemo {
    private MobileServiceTable typedTable;
    private TableQueryCallback typedCallback;
    private MobileServiceJsonTable jsonTable;

    public QueryChangesDemo(MobileServiceClient client) {
        typedTable = client.getTable(TodoItem.class);
        jsonTable = client.getTable("TodoItem");
        typedCallback = typedCallback = new TableQueryCallback() {
            @Override
            public void onCompleted(List results, int count, Exception error, ServiceFilterResponse response) {
            }
        };
    }

    public void before() {
        // Not assigning to a query object - no changes needed
        typedTable.where().field("complete").eq(false).execute(typedCallback);

        // The type of 'query' will change
        MobileServiceQuery> query = typedTable.where().field("complete").eq(false);
        query.execute(typedCallback);

        // The TableJsonQueryCallback.onCompleted will lose the 'count' parameter
        TableJsonQueryCallback jsonCallback = new TableJsonQueryCallback() {
            @Override
            public void onCompleted(JsonElement result, int count, Exception error,
                    ServiceFilterResponse response) {
            }
        };

        // Not assigning to a query object - no changes needed
        jsonTable.where().field("complete").eq(false).execute(jsonCallback);

        // The type of the 'jsonQuery' variable will change
        MobileServiceQuery jsonQuery = jsonTable.where().field("complete").eq(false);
        jsonQuery.execute(jsonCallback);
    }
}

In the new SDK (2.x), if you need to assign the query to a variable before executing, then you’ll need to change the variable type, otherwise the change won’t be necessary.

    public void after() {
        // Not assigning to a query object - no changes needed
        typedTable.where().field("complete").eq(false).execute(typedCallback);

        // The type of 'query' changes to ExecutableQuery
        ExecutableQuery query = typedTable.where().field("complete").eq(false);
        query.execute(typedCallback);

        // The TableJsonQueryCallback.onCompleted does not have the the 'count' parameter anymore
        TableJsonQueryCallback jsonCallback = new TableJsonQueryCallback() {
            @Override
            public void onCompleted(JsonElement result, Exception error,
                    ServiceFilterResponse response) {
            }
        };

        // Not assigning to a query object - no changes needed
        jsonTable.where().field("complete").eq(false).execute(jsonCallback);

        // The type of the 'jsonQuery' variable will change
        ExecutableJsonQuery jsonQuery = jsonTable.where().field("complete").eq(false);
        jsonQuery.execute(jsonCallback);
    }

One more change above: the method onCompleted of the TableJsonQueryCallback interface used to have a ‘count’ parameter, but its value was never populated (if it was used it was incorrect). We now removed this parameter to better align the Android API with with the managed code.

Service filters

As I mentioned before, service filters are used internally and in advanced scenarios, and we changed them from a callback-based to a futures-based definition. As such, the methods ServiceFilter.handleRequest and NextServiceFilterCallback.onNext now return a ListableFuture instead of taking a callback parameter. Aside from this change, their behavior is the same. For example, here’s the “identity” service filter, one which passes the message through the next filter in the pipeline:

ServiceFilter identityFilter = new ServiceFilter() {
    @Override
    public ListenableFuture handleRequest(
            ServiceFilterRequest request, NextServiceFilterCallback next) {
        return next.onNext(request);
    }
};

As I mentioned, only advanced users will need to work with filters, so it’s possible that you won’t need to worry about this change.

Wrapping up

Addressing feedback which we received on our SDK, we’ve added futures support for our Android SDK. There were some breaking changes which we listed above, so we’re releasing the SDK in an alpha version so that we can address any additional comments / suggestions / complaints before we release it broadly. Later this week I’ll write about the offline support which we added, so that the Android SDK is now on par with the managed and iOS versions of the SDK. Please try it out, by downloading the SDK at https://aka.ms/Iajk6q, and send us feedback either as comments in this post or in our MSDN forums.

  • Explore

     

    Let us know what you think of Azure and what you would like to see in the future.

     

    Provide feedback

  • Build your cloud computing and Azure skills with free courses by Microsoft Learn.

     

    Explore Azure learning


Join the conversation