I'm new to Android Development
and I'm trying to develop my first Android app which gets data from some public APIs
using Android Volley
.
I'm using singleton Volley Request Queue
which is initialized in the launcher activity. I am successfully able to parse the JSON contents and display them on a Fragment layout/view (uses RecyclerView & CardView)
when I set my RecyclerView
adapters INSIDE the Volley's JsonObjectRequest
.
The following code does display data, but suffers from time race condition. Note: RvJoiner is a library which merges multiple adapters and makes a single adapter ordered by FIRST COME FIRST SERVE basis.
My Fragment class is as follows:
public class Fragment1 extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.recylcer_main, container, false);
ParseJSON parseJSON = new ParseJSON(v);
parseJSON.makeRequest1();
parseJSON.makeRequest2();
return v;
}
}
My ParseJSON class is as follows
public class ParseJSON {
private static final String URL1 = "some url";
private static final String URL2 = "some other url";
private static final String TAG = "ParseJSON";
private RequestQueue requestQueue;
private boolean FLAG_REQUEST1_FETCHED;
private boolean FLAG_REQUEST2_FETCHED;
private ArrayList<status1> status1ArrayList;
private ArrayList<status2> status2ArrayList;
private Context context;
private RvJoiner rvJoiner;
private View view;
ProgressDialog pd;
ParseJSON (View v){
this.view= v;
this.context=v.getContext();
pd = ProgressDialog.show(v.getContext(), "Please Wait", "Getting Data from APIs", true);
requestQueue = AppController.getInstance(v.getContext()).getRequestQueue();
rvJoiner = new RvJoiner();
}
public void makeRequest1() {
JsonObjectRequest request1 = new JsonObjectRequest(Request.Method.GET, URL1,
null, new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
try {
/* Parsing Stuff and storing it in status1ArrayList */
FLAG_REQUEST1_FETCHED=true;
Status1Adapter status1Adapter = new Status1Adapter(status1ArrayList);
RecyclerView recList = (RecyclerView) view.findViewById(R.id.cardList);
recList.setLayoutManager(new LinearLayoutManager(context));
rvJoiner.add(new JoinableAdapter(status1Adapter));
recList.setAdapter(rvJoiner.getAdapter());
pd.dismiss();
}
} catch (JSONException e) {}
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {}
});
AppController.getInstance(context).addToRequestQueue(request1);
}
public void makeRequest2() {
JsonObjectRequest request2 = new JsonObjectRequest(Request.Method.GET, URL2,
null, new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
try {
/* Parsing stuff and storing it inside ArrayList status2ArrayList */
FLAG_REQUEST2_FETCHED=true;
Status2Adapter status2Adapter = new Staus2Adapter(status2ArrayList);
RecyclerView recList = (RecyclerView) view.findViewById(R.id.cardList);
recList.setLayoutManager(new LinearLayoutManager(context));
rvJoiner.add(new JoinableAdapter(status2Adapter));
}
} catch (JSONException e) {}
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {}
});
AppController.getInstance(context).addToRequestQueue(request2);
}
public boolean isStatusFetched(){
return FLAG_REQUEST1_FETCHED && FLAG_REQUEST2_FETCHED;
}
public ArrayList<status1> getstatus1ArrayList() {
return status1ArrayList;
}
public ArrayList<status2> getstatus2ArrayList() {
return status2ArrayList;
}
}
In the above code, I'm having a race condition. Since Volley
network calls are asynchronous in nature, I have no control on which request will get completed and displayed on my Fragment CardView
first. (i.e any of rvJoiner.add()
requests can be executed first)
I would like to make my UI consistent i.e I want Request1 adapter to be added to RvJoiner first and then the Request2.
If possible, I would like to move all my code that sets adapters and joins them from JsonObjectRequest to my Fragment's onCreateView method. So, in this way, I have a control on the order of adapters. However, then I need a method which checks the value of FLAG_REQUEST1_FETCHED
and FLAG_REQUEST2_FETCHED
via isStatusFetched
method continuously.
Code for the Fragment class will be
public class Fragment1 extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.recylcer_main, container, false);
ParseJSON parseJSON = new ParseJSON(v);
parseJSON.makeRequest1();
parseJSON.makeRequest2();
while(!parseJSON.isDataFetched()){
/* I want to wait here till both status1ArrayList and status2ArrayList gets populated with data in ParseJSON. In this way I can control the order in which adapters are added inside RvJoiner. If I don't wait here I will get NullPointerException on runtime since Volley calls are asynchronous and getStatus1ArrayList/getStatus2ArrayList will most probably return null. But how to wait here without consuming too much CPU power? */
}
ArrayList<status1> status1ArrayList = parseJSON.getstatus1ArrayList();
ArrayList<status2> status2ArrayList = parseJSON.getstatus2ArrayList();
Status1Adapter status1Adapter = new Status1Adapter(status1ArrayList);
Status2Adapter status2Adapter = new Status2Adapter(status2ArrayList);
RecyclerView recList = (RecyclerView) v.findViewById(R.id.cardList);
recList.setLayoutManager(new LinearLayoutManager(v.getContext()));
RvJoiner rvJoiner = new RvJoiner();
/* Problem solved as I'm adding adapters in the order I want */
rvJoiner.add(new JoinableAdapter(status1Adapter));
rvJoiner.add(new JoinableAdapter(status2Adapter));
recList.setAdapter(rvJoiner.getAdapter());
return v;
}
}
One solution can be using callbacks. I read somewhere about them, but I'm not sure if it solves my problem of 'multiple request at the same time while maintaining order'.
Another solution would be to restrict my Volley Queue to handle one request at one time only but that would increase the time taken to fetch and serve data. This is my last choice.
I am virtually out of ideas and would like someone to help me so that I can control the order of setting my adapters and maintain a consistent UI. If you need any other information, please tell me.
Thanks.
This is how avoiding race conditions for two requests work in general. You should work with callbacks. The implementation of your onResponse methods are callbacks because those methods are called after one request is done. Response handling works on the UI thread right ? So the responses can just be handled one by the other. This means you just have to maintain order there. Extract the work you would like to do after getting one response. You need some boolean flags indicating whether your requests are done. Pseudocode would look like this:
request1Done = false;
request2Done = false;
doRequest1();
doRequest2();
onResponse1() {
doWorkForRequest1(); // always start handling the response
request1Done = true;
if (request2Done) { // if this is true, request2 was faster than request1
doWorkForRequest2();
}
};
onResponse2() {
request2Done = true;
if (request1Done) { // request1 did its work, no its request2's turn
doWorkForRequest2();
}
};
So basically you should fix your onReponse methods. Hope this will help you. :)