Android - Asynchronous Network Calls - Response dependent on each other
Asked Answered
P

4

6

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 ?

Papageno answered 15/3, 2016 at 8:0 Comment(3)
are both server-calls independant from each other or does server1-response supply you with data needed for server2-callSled
@Sled - they are independent network calls fetching me same kind of data that I have to compare in a graph.Papageno
If you show us your code then the community can show you how to modify the code. Abstract answer: start both request-s together and in both onResponse check if the other response has already been received and if yes start processing.Sled
P
4

@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;
    }
}
Perm answered 15/3, 2016 at 12:54 Comment(10)
At least use two flags.Colorable
@tommus: You can achieve the goal with only one flag in this case. I've implemented such thing once and it is working perfectly fine. I would love to know why should we go for 2 flags in such type of scenarios.Perm
According to the situation that author of the question describes - he wants to update view only when both requests are completed. Your approach updates the view whenever any of the requests are finished.Colorable
@tommus: I think, my approach would update the view only when both are executed. Please have a look on code. Anyone that executes first, will turn the flag ON. 2nd one will check the state of flag. If it is already ON, it will populate the views.Perm
Yes. My bad, you're right. Remeber that you do not need to write if(flag == true) {}. It is enough to use just if(flag) {}.Colorable
Wow. Didn't think the solution would be this easy. Thanks @Ahmad PS: Just curious to know what difference would it make in the above approach if I don't use the volatile keyword ?Papageno
Also you mentioned that @tommus solution approach is the best approach. Just wondering why would you not use a simpler approach like this ? What case/scenario would be the best to adopt tommus's approach ?Papageno
My solution is more extendable and generic. The way that I described you can implement a lot more complex flow than just "do something in parallel and show". :)Colorable
declaring volatile is good when you're changing value of variable in different threads. more details here: javamex.com/tutorials/synchronization_volatile.shtml. @tommus 's reply is generic and can be implemented for complex scenarios.Perm
Yes, indeed. I think, @tommus's approach would be the best if we have a little more complicated scenario. Say, we receive data from 4-5 different API's(instead of just 2 which was my requirement) and want to compare the data received in a graph only after receiving the response from all 4-5 API's. So I think that approach will come in handy then. I'll read more about Observable and RxJava in the meantime. Thanks for your help guys, specially tommus. Really appreciate it :)Papageno
C
7

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. :)

Colorable answered 15/3, 2016 at 8:53 Comment(7)
Really nice answer. Although, I think AndroidObservable overcomplicates it a bit - I think that for a RxJava newcomer it would be easier to start with plain ObservableAllyl
Another addition - as recently announced by Google, lambdas will be supported with new Jack compiler. So, in (hopefully) not so distant future you won't need Retrolambda.Allyl
@DmitryZaitsev - maybe but I think that - in the end - AndroidObservable simplifies everything. Using simple Observable forces user to remember to handle results with runOnUiThread method etc. Investing some time in learning RxAndroid pays for itself really quickly. And it's great to hear about introducing support for lambdas soon.Colorable
Not really, in your case all AndroidObservable does is unsubscribes from source in onDestroy. You're already observing values on AndroidSchedulers.mainThread() - runOnUiThread is not needed. Moreover, I find it too dependent on Activity - if you use something like MVP, this might become a problem.Allyl
@tommus - Thanks for the detailed explanation of this library. But just like Dmitry mentioned for a RxJava newcomer like me, this complicates things a bit. Also I want to use some native way of handling this situation because I don't prefer adding new libraries unless its the only way out.Papageno
@Papageno - I've added native implementation. Take a look.Colorable
Thanks @tommus. I'll try it out and let you know all.Papageno
P
4

@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;
    }
}
Perm answered 15/3, 2016 at 12:54 Comment(10)
At least use two flags.Colorable
@tommus: You can achieve the goal with only one flag in this case. I've implemented such thing once and it is working perfectly fine. I would love to know why should we go for 2 flags in such type of scenarios.Perm
According to the situation that author of the question describes - he wants to update view only when both requests are completed. Your approach updates the view whenever any of the requests are finished.Colorable
@tommus: I think, my approach would update the view only when both are executed. Please have a look on code. Anyone that executes first, will turn the flag ON. 2nd one will check the state of flag. If it is already ON, it will populate the views.Perm
Yes. My bad, you're right. Remeber that you do not need to write if(flag == true) {}. It is enough to use just if(flag) {}.Colorable
Wow. Didn't think the solution would be this easy. Thanks @Ahmad PS: Just curious to know what difference would it make in the above approach if I don't use the volatile keyword ?Papageno
Also you mentioned that @tommus solution approach is the best approach. Just wondering why would you not use a simpler approach like this ? What case/scenario would be the best to adopt tommus's approach ?Papageno
My solution is more extendable and generic. The way that I described you can implement a lot more complex flow than just "do something in parallel and show". :)Colorable
declaring volatile is good when you're changing value of variable in different threads. more details here: javamex.com/tutorials/synchronization_volatile.shtml. @tommus 's reply is generic and can be implemented for complex scenarios.Perm
Yes, indeed. I think, @tommus's approach would be the best if we have a little more complicated scenario. Say, we receive data from 4-5 different API's(instead of just 2 which was my requirement) and want to compare the data received in a graph only after receiving the response from all 4-5 API's. So I think that approach will come in handy then. I'll read more about Observable and RxJava in the meantime. Thanks for your help guys, specially tommus. Really appreciate it :)Papageno
C
0

Not Perfect But I hope this logic works for you

 onDatafromServer1Fetched{
    flag1 = true;
    }
    onDataFromServer2Fetched{
    flag2=true;
    }

main(){
        boolean flag1  = false;
        boolean flag2 =false;

    getDataFromSerVer1();
    getDataFromServer2();
    while(!flag1 && !flag2){/**no code required here*/}
    loadView();
 }
Cnidoblast answered 23/3, 2016 at 12:18 Comment(0)
V
-1

if the two request has the same data format, and hold by the same container. Why not check the container's size simply.

public class MainActivity extends AppCompatActivity {

private List<Data> myList = new ArrayList<>();
private boolean error; // if one request got error, another one need to konw,
//use to handle response size 0 or other error

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    GsonRequest request1 = new GsonRequest(params... , this.mClazz, null, new Response.Listener() {
        @Override
        public void onResponse(Object response) {
            boolean updateUI = false;
            if(myList.size()>0){ //or > the init size
                updateUI = true;
            }

            myList.addAll(response);

            if(updateUI){
                notifydatasetchange();
            }
        }
    }, new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {

        }
    });
    GsonRequest request2 = new GsonRequest(params... , this.mClazz, null, new Response.Listener() {
        @Override
        public void onResponse(Object response) {
            boolean updateUI = false;
            if(myList.size()>0){ //or > the init size
                updateUI = true;
            }

            myList.addAll(response);

            if(updateUI){
                notifydatasetchange();
            }
        }
    }, new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {

        }
    });
}

}

Vanettavang answered 15/3, 2016 at 8:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.