Android - Asynchronous Network Calls - The Answer Depends On Each Other

I faced this situation when developing an Android application today, when I needed to display graphs based on responses from two different APIs. I use Volley, and what I did was make a serial network call. I made the first request, and in the onResponse method of this request, I made the second request. And then I visualize the view (graph) in the onResponse method of the second request.

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

  • getDataFromServer1 (network call to receive data from one server)
  • getDataFromServer2 (network call to receive data from another server)
  • loadView (display graphs based on data received from two network calls)

How can I do it? Can someone throw some light on him?

+6
source share
4 answers

@tommus is the best approach.


If you want to use a simple or less approach to the code, you can use the boolean flag to ensure that both of them execute and move forward depending on the condition.

Declare a mutable boolean to be used as a flag.

 private volatile boolean flag = false; 
Flag

will be false at startup. Now call both web services. Any running service will return this TRUE flag.

 getDataFromServer1(); function void onCompleteServer1() { if(flag) { loadViews(); } else { flag = true; } } getDataFromServer2(); onCompleteServer2Request() { if(flag) { loadViews(); } else { flag = true; } } 
+3
source

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() { // 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; } } 

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) { // Show repositories in your fragment. } private void showErrors() { // Pops up some contextual information / help. } 

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."); // No need to test // if intent.getAction().equals(TASKS_FINISHED_ACTION) {} // as we used filter. } } 

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

+5
source

Not perfect, but I hope this logic will work 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(); } 
0
source

if two requests have the same data format and are held by the same container. Why not just check the container size.

 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) { } }); } 

}

-one
source

All Articles