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 MobileServiceTabletable = 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 MobileServiceTabletable = 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:
publicvoid 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:
publicListenableFuture 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
- Renamed to Query, with two new subclasses: ExecutableQuery
- MobileServiceQueryOperations
- Class with the static operations used for creating typed queries. Renamed to QueryOperations.
- QueryOrder
- MobileServiceQuery (*)
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
public class QueryChangesDemo { private MobileServiceTabletypedTable; 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 ExecutableQueryExecutableQuery 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 ListenableFuturehandleRequest( 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.