Version 1 - with external libraries
This is a great example when RxAndroid is suitable (or more generally, any infrastructure supporting event-driven programming).
Let's say we have the following domain classes that allow us to retrieve some data from web services:
Repository Class:
public class Repository { protected String name; public Repository(String name) { this.name = name; } public String getName() { return name; } }
Service Interface:
public interface GitService { List<Repository> fetchRepositories(); }
The first service implementation:
public class BitbucketService implements GitService { @Override public List<Repository> fetchRepositories() {
Second service implementation:
public class GithubService implements GitService { @Override public List<Repository> fetchRepositories() {
Above, we can easily create an observable (an object that looks like if something happened) that checks to see if we successfully downloaded data from both services. This responsibility has the following method:
public Observable<List<Repository>> fetchRepositories() { // This is not the best place to instantiate services. GitService github = new GithubService(); GitService bitbucket = new BitbucketService(); return Observable.zip( Observable.create(subscriber -> { subscriber.onNext(github.fetchRepositories()); }), Observable.create(subscriber -> { subscriber.onNext(bitbucket.fetchRepositories()); }), (List<Repository> response1, List<Repository> response2) -> { List<Repository> result = new ArrayList<>(); result.addAll(response1); result.addAll(response2); return result; } ); }
The only thing to do is to execute the task somewhere (example in the onCreate method):
@Override protected void onCreate(Bundle savedInstanceState) { (...) AndroidObservable .bindActivity(this, fetchRepositories()) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(repos -> showRepositories(repos), error -> showErrors()); }
The code above is where magic happens. Here:
- defines the context of the task
- identified the need to create asynchronous threads,
- determined that the result should be processed in the user interface stream when the task is completed,
- determined which methods to call to process results and errors.
Above, in subscribe we skip lambda calls to display results / errors to the user:
private void showRepositories(List<Repository> repositories) {
Since Android is currently using SDK 1.7, we need to use a library that allows us to use lambdas in 1.7-compatible code. Personally, I use retrolambda for this case.
If you do not like lambdas, you always have the opportunity to implement anonymous classes, where it is necessary otherwise.
Thus, we can avoid writing a lot of Android boiler room code.
Version 2 - no external libraries
If you do not want to use external libraries, you can achieve the same with AsyncTasks with Executor .
We will reuse the domain classes described above: Repository , GitService , GithubService and BitbucketService .
As we want to interview how many tasks were completed, let me introduce some kind of counter in our activity:
private AtomicInteger counter = new AtomicInteger(2);
We will share this object in our asynchronous tasks.
Next, we must implement the task itself, for example:
public class FetchRepositories extends AsyncTask<Void, Void, List<Repository>> { private AtomicInteger counter; private GitService service; public FetchRepositories(AtomicInteger counter, GitService service) { this.counter = counter; this.service = service; } @Override protected List<Repository> doInBackground(Void... params) { return service.fetchRepositories(); } @Override protected void onPostExecute(List<Repository> repositories) { super.onPostExecute(repositories); int tasksLeft = this.counter.decrementAndGet(); if(tasksLeft <= 0) { Intent intent = new Intent(); intent.setAction(TASKS_FINISHED_ACTION); sendBroadcast(intent); } } }
Here's what happened:
- In the constructor, we introduced the common counter and service that were used to retrieve the data
- at
doInBackground we delegated the management of our dedicated service, - in
onPostExecute , which we tested if all expected tasks are completed, - after hard work - the transfer to action was sent.
Then we should receive a potential broadcast that informs us that the work is done. In this case, we implemented a broadcast receiver:
public class FetchBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Log.d("onReceive", "All tasks has been finished.");
Instead of a registration message, you need to update your view.
The mentioned constant TASKS_FINISHED_ACTION names your filter:
private static final String TASKS_FINISHED_ACTION = "some.intent.filter.TASKS_FINISHED";
Do not forget to initialize and register the receiver and filters both in your activity and in the manifest.
Activity:
private BroadcastReceiver receiver = new FetchBroadcastReceiver(); @Override protected void onResume() { super.onResume(); IntentFilter filter = new IntentFilter(); filter.addAction(TASKS_FINISHED_ACTION); registerReceiver(receiver, filter); }
Manifest (inside the application tag):
<receiver android:name=".TestActivity$FetchBroadcastReceiver"/>
I put the receiver class as public in TestActivity , so it looks weird.
In the manifest, you must also register your action (activity filter for activity):
<action android:name="some.intent.filter.TASKS_FINISHED"/>
Remember to unregister the receiver in the onPause() method.
Having prepared the operation, you can perform your tasks somewhere (for example, in the onCreate method, as in the first example):
if(Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB) { new FetchRepositories(counter, new GithubService()) .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); new FetchRepositories(counter, new BitbucketService()) .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } else { // Below Honeycomb there was no parallel tasks. new FetchRepositories(counter, new GithubService()).execute(); new FetchRepositories(counter, new BitbucketService()).execute(); }
As you can see, parallel tasks will only be performed on Honeycomb and higher. Before this version of the Android thread pool can contain up to 1 task.
At least we used some injection options and strategies. :)