Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android - Asynchronous Network Calls - Response dependent on each other

I just encountered this sort of a situation while developing an Android application today where I was required to render graphs based on responses from 2 different API's. I'm using Volley and what I did is I made a sequential network call i.e I made the 1st request, and in the onResponse method of that request I made the 2nd request. And then I render the view (the graph) in the onResponse method of 2nd request.

Now I want to optimize this situation. I want to know a way I can make these 2 network calls asynchronously where I render the view only after receiving responses from both API's. So, say I have 3 modular methods namely -

  1. getDataFromServer1 (network call to get data from one server)
  2. getDataFromServer2 (network call to get data from another server)
  3. loadView (render graphs based on data received from 2 network calls)

How do I go about it ? Can someone throw some light upon it ?

like image 324
Shubhral Avatar asked Mar 15 '16 08:03

Shubhral


2 Answers

Version 1 - with external libraries

This is a perfect example where RxAndroid comes handy (or more general - any framework that supports event driven programming).


Let's say we have the following domain classes that allows us to fetch 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();
}

First service implementation:

public class BitbucketService implements GitService {
    @Override
    public List<Repository> fetchRepositories() {
        // Time consuming / IO consuming task.
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            // Swallow exception.
        }

        List<Repository> result = new ArrayList<>();
        result.add(new Repository("http://some-fancy-repository.com/"));
        return result;
    }
}

Second service implementation:

public class GithubService implements GitService {
    @Override
    public List<Repository> fetchRepositories() {
        // Time consuming / IO consuming task.
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            // Swallow exception.
        }

        List<Repository> result = new ArrayList<>();
        result.add(new Repository("http://some-fancier-repository.com/"));
        return result;
    }
}

Having above we can easily create an observable (an object that looks if something had happened) that checks whether we have successfully downloaded data from both services. This responsibility have 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 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 a place where magic happens. Here is:

  • defined a context of the task,
  • defined a necessity of creating async threads,
  • defined that the result should be handled in UI thread when the hard job ends,
  • defined which methods to call to handle results and errors.

Above, in subscribe we are passing lambda calls to show results/errors to the user:

private void showRepositories(List<Repository> repositories) {
    // Show repositories in your fragment.
}

private void showErrors() {
    // Pops up some contextual information / help.
}

As Android currently uses SDK 1.7 there is a need to use a library that allows us to use lambdas in the 1.7-compliant code. Personally, I am using retrolambda for this case.

If you do not like lambdas - you always have a possibility of implementing anonymous classes wherever necessary otherwise.

This way we can avoid writing a lot of Android's boiler-plate code.


Version 2 - no external libraries

If you don't want to use external libraries you can achieve similar with AsyncTasks accompanied by Executor.


We will reuse described above domain classes: Repository, GitService, GithubService and BitbucketService.

As we want to poll how many tasks has been finished, let's introduce some kind of counter into our activity:

private AtomicInteger counter = new AtomicInteger(2);

We will share this object in our asynchronous tasks.

Next, we have to implement a task itself, for example like this:

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 is what has happened:

  • in constructor we've injected shared counter and service that was used to fetch data,
  • in doInBackground method we've delegated control to our dedicated service,
  • in onPostExecute method we've tested if all expected tasks has finished,
  • after all the tough job - a broadcast to the activity has been sent.

Next we have to receive potential broadcast that informs us the job has been done. For that case we implemented broadcast receiver:

public class FetchBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d("onReceive", "All tasks has been finished.");
        // No need to test 
        // if intent.getAction().equals(TASKS_FINISHED_ACTION) {}
        // as we used filter.
    }
}

Instead of logging message - you have to update your view.

Mentioned constant TASKS_FINISHED_ACTION names your filter:

 private static final String TASKS_FINISHED_ACTION = "some.intent.filter.TASKS_FINISHED";

Remember to initialize and register receiver and filters in both - your activity and 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 application tag):

<receiver android:name=".TestActivity$FetchBroadcastReceiver"/>

I put receiver class as public in TestActivity so it looks strange.

In manifest you have to also register your action (inside activity intent filter):

<action android:name="some.intent.filter.TASKS_FINISHED"/>

Remember to unregister your receiver in onPause() method.


Having prepared activity you can execute your tasks somewhere (for example in onCreate method like 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 noticed, parallel tasks will run only on Honeycomb and above. Before this version of Android thread pool can hold up to 1 task.


At least we've used some dependency injection and strategy patterns. :)

like image 58
Tomasz Dzieniak Avatar answered Nov 20 '22 19:11

Tomasz Dzieniak


@tommus solution is the best approach.


If you want to go with simple or less code approach, You can use a boolean flag to ensure that both are executed and move forward based on condition.

Declare a volatile boolean variable that will be used as a flag.

private volatile boolean flag = false;

flag would be false at start. Now, make call to both webservices. Any service that is executed will turn this flag TRUE.

getDataFromServer1();
function void onCompleteServer1() {
    if(flag) {
       loadViews();
    } else {
       flag = true;
    }
}


getDataFromServer2();
onCompleteServer2Request() {
    if(flag) {
       loadViews();
    } else {
       flag = true;
    }
}
like image 36
Ahmad Raza Avatar answered Nov 20 '22 19:11

Ahmad Raza