Back

Task Queues With Goro

This blog hasn’t had a real technical article for a while. It’s time to fix this!

Today we are announcing Goro, an Android library for organizing your asynchronous tasks in queues.
When developing Android apps, we often want to ensure that certain operations are executed in the correct order.

To illustrate, imagine you have a bunch of controls which cause some back-end API calls. You have to be sure that requests are sent and processed by the back-end in the same order in which the user interacted with those controls.

And now, let’s imagine that handling some of those calls, you’ll have to modify your local database, and you want to be sure that your write operations are executed sequentially as well.

Here’s where Goro will help.

From the example above, we conclude that we need two task queues: one for back-end API methods invocation and another for database write operations.

Let’s give them names…

[code language=”java”]
static final String QUEUE_REST = "rest";
static final String QUEUE_DB = "db";
[/code]

… and create our hero.

[code language=”java”]
Goro goro = new Goro();
[/code]

We’ll use RxJava and Retrofit to send requests and get results.

[code language=”java”]
interface Backend {
@FormUrlEncoded @POST("/board/monochrome")
Observable<BoardState> setMonochrome(
@Field("monochrome") boolean monochrome
)
}

RestAdapter adapter = new RestAdapter.Builder()
.setEndpoint("http://my.server")
.setExecutors(goro.getExecutor(QUEUE_REST), null)
.build();

Backend backend = adapter.create(Backend.class);
[/code]

Here we use Goro for the first time to schedule backend calls on the REST queue. Now let’s consider a possible control action listener sending requests and updating a database:

[code language=”java”]
backend
.setMonochrome(monochromeCheckbox.isChecked())
.subscribeOn(Schedulers.executor(goro.getExecutor(QUEUE_DB)))
.subscribe(new Action1<BoardState>() {
@Override
public void call(BoardState board) {
database.updateBoard(board);
}
});
[/code]

Here we’ve used Goro again to have a result subscriber be executed on the DB queue.

Goro organizes your tasks in queues and executes them on AsyncTask.THREAD_POOL_EXECUTOR in order to minimize the count of threads used in your application. You may also customize the executor Goro delegates tasks to, passing it in Goro’s constructor parameter.

We usually do not create Goro instance directly, but run it in a Service context to ensure that our app is not the first candidate for killing while scheduled tasks are being executed, and user sends our app to the background pressing the home button on a device.

Thus Goro library also contains a dedicated service implementation: GoroService.

You may either get Goro instance after binding to this service or send tasks to it in Intents.

Though the last variant makes you design tasks as Parcelables, it gives you a lot of benefits: you may easily pass a created task to AlarmManager or set as an action for some notification:

[code language=”java”]
// task example
public class SimpleTask implements Callable, Parcelable {

public static final Creator CREATOR = new Creator() {
@Override
public SimpleTask createFromParcel(Parcel source) {
return new SimpleTask(source.readString());
}
@Override
public SimpleTask[] newArray(int size) {
return new SimpleTask[size];
}
}

private final String message;

public SimpleTask(String message) {
this.message = message;
}

@Override
public Void call() {
// Actually this is what we really do not like:
// there is no way to easily inject context to your task
// But we are actively thinking about it :)
Context context = MyApplication.get();
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
}

@Override
public int describeContents() {
return 0;
}

@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(message);
}
}

Intent taskIntent =
GoroService.taskIntent(context, new SimpleTask("Hello!"));
PendingIntent operation =
PendingIntent.getService(context, 0, taskIntent, 0);

// Schedule with AlarmManager
alarmManager.set(AlarmManager.ELAPSED_REALTIME, time, operation);

// set as notification action
new Notification.Builder(context)
.setContentIntent(operation);

// or schedule it now
context.startService(taskIntent);
[/code]

You’ll find example how to use Goro via binding to GoroService in our sample. Actually, this usage complicates things a bit, so we are working on a solution to simplify service binding and using Goro instance.

More details are available at the Github page.

Happy queueing!

February 8, 2014

Development