How to make concurrent GET calls using Volley?
Asked Answered
K

5

6

I have 3 API GET calls. The problem I'm facing with my approach is, the app is able to fetch the data successfully from two APIs and I'm able to display it on UI as well. But, for the third API call, due to the below error, the data that is being shown previously disappears which is bad.

D/Volley: [380] BasicNetwork.logSlowRequests: HTTP response for request=<[ ] http://example.com/api/search/getTwitterData?limit=10&tag=JavaScript 0x865f5dc2 NORMAL 3> [lifetime=6683], [size=10543], [rc=200], [retryCount=0]

How do I make concurrent API GET calls using Volley without losing the data on UI. Could anyone please guide me?

Here are excerpts from my code.

public class StaggeredSearchActivity extends AppCompatActivity {

    ...

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

        requestQueue = Volley.newRequestQueue(this);

        Intent intent = getIntent();
        String searchText = intent.getStringExtra("searchText");

        // Three concurrent API GET Calls  
        getMediumData(searchText);
        getExampleData(searchText);
        getGoogleData(searchText);

        recyclerView = findViewById(R.id.staggered_recycler_view);
        staggeredGridLayoutManager = new StaggeredGridLayoutManager(2, LinearLayoutManager.VERTICAL);
        recyclerView.setLayoutManager(staggeredGridLayoutManager);
    }

    ArrayList<StaggeredCustomCard> dataset = new ArrayList<>();

    private void getMediumData(String searchText) {
        progressBar = findViewById(R.id.progressBar);
        progressBar.setVisibility(View.VISIBLE);

        String url = UrlConstants.getUrl() + searchText;

        JsonObjectRequest jsonObjectRequest = new JsonObjectRequest
                (Request.Method.GET, url, null, new Response.Listener<JSONObject>() {

                    @Override
                    public void onResponse(JSONObject response) {
                        try {
                            progressBar.setVisibility(View.INVISIBLE);
                            JSONArray array = response.getJSONArray("mediumposts");

                            ...

                            dataset.add(new StaggeredCustomCard(user, userpost, postdate));
                            }
                            staggeredGridAdapter = new StaggeredGridAdapter(StaggeredSearchActivity.this, dataset);
                            recyclerView.setAdapter(staggeredGridAdapter);
                        } catch (JSONException e) {
                            e.printStackTrace();
                        }
                    }

                }, new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        // TODO: Handle error
                        progressBar.setVisibility(View.INVISIBLE);
                    }
                });

        jsonObjectRequest.setRetryPolicy(new DefaultRetryPolicy(
                DefaultRetryPolicy.DEFAULT_TIMEOUT_MS * 15,
                DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
                DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
        requestQueue.add(jsonObjectRequest);
    }

    private void getExampleData(String searchText) {

        ...

        JsonArrayRequest jsonArrayRequest = new JsonArrayRequest
                (Request.Method.GET, url, null, new Response.Listener<JSONArray>() {

                    @Override
                    public void onResponse(JSONArray response) {
                        try {
                            ...
                            dataset.add(new StaggeredCustomCard(user, userpost, postdate));
                                staggeredGridAdapter = new StaggeredGridAdapter(StaggeredSearchActivity.this, dataset);
                                recyclerView.setAdapter(staggeredGridAdapter);
                            }
                        } catch (JSONException e) {
                            e.printStackTrace();
                        }
                    }
                }, new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        ...
                    }
                });

        jsonArrayRequest.setRetryPolicy(new DefaultRetryPolicy(
                DefaultRetryPolicy.DEFAULT_TIMEOUT_MS * 15,
                DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
                DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
        requestQueue.add(jsonArrayRequest);
    }

    private void getGoogleData(String searchText) {
        ...

        JsonObjectRequest jsonObjectRequest = new JsonObjectRequest
                (Request.Method.GET, url, null, new Response.Listener<JSONObject>() {

                    @Override
                    public void onResponse(JSONObject response) {
                        ...

                        dataset.add(new StaggeredCustomCard(user, userpost, postdate));

                            }
                            staggeredGridAdapter = new StaggeredGridAdapter(StaggeredSearchActivity.this, dataset);
                            recyclerView.setAdapter(staggeredGridAdapter);
                        } catch (JSONException e) {
                            e.printStackTrace();
                        }
                    }

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

        jsonObjectRequest.setRetryPolicy(new DefaultRetryPolicy(
                DefaultRetryPolicy.DEFAULT_TIMEOUT_MS * 15,
                DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
                DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
        requestQueue.add(jsonObjectRequest);
    }
}
Knoll answered 16/9, 2019 at 16:43 Comment(7)
Means your old data will lost? Once the new API response arrived.Solorio
Yeah. The data disappears from UI once I see that logSlowRequests log in the LogCat for the third API response. @KintanPatelKnoll
If your problem is fixed then approving the answer is more useful for use.Solorio
Trust me on this, don't use volley. As the project grows, it will be difficult to test and manage.Stammer
It is fixed. @KintanPatel Can't believe it was that simple mistake :D Thank you for your time and the answer.Knoll
Retrofit and RXJava,RX Android is more preferable.Solorio
Alright @KintanPatel Appreciate the suggestions.Knoll
S
3

The problem is that you are initializing Adapter every time that's why your data will be lost once a new API call. I prefer below approach so that can help you, Add Data in ArrayList and notify adapter,

Add this line in onCreate,

staggeredGridAdapter = new StaggeredGridAdapter(StaggeredSearchActivity.this, dataset);
recyclerView.setAdapter(staggeredGridAdapter);

Changes in API Callback Response :

                             ...

dataset.add(new StaggeredCustomCard(user, userpost, postdate));

After loop add below line

staggeredGridAdapter.notifyDataSetChanged();

Changes in Adapter

 private ArrayList<StaggeredCustomCard> dataSet;
        private Context context;

        public MyAdapter(ArrayList<StaggeredCustomCard> dataSet, Context context) {
            this.data = data;
            this.context = context;
        }

Note : Don't create new object on adapter.

Solorio answered 3/10, 2019 at 8:38 Comment(1)
That was it Kintan. You rock! It is working as expected now. Appreciate your explanation and the answer.Knoll
C
2

The approach just doesn't make a lot of sense. Once you get a response from one of the three endpoints, you seem to create a new Adapter and attach it to the recycler with a random "notifyDataSetChanged" every time...

  1. Maybe look at using a ViewModel with a service layer and network layer that deal with the business login.
  2. The ViewModel updates/posts a MutableLiveData> when a callback from one of the Network methods responds from the endpoints...merging the three pieces of data.
  3. The activity just observes the ViewModel's MutableLiveData and uses a DiffUtil to update the look/cards in the recycler.

enter image description here

Chemisorb answered 16/9, 2019 at 21:39 Comment(0)
F
2

The best way is to register your LiveData in the StaggeredSearchActivity in onCreate method and listen for db changes like you did. In the each success responce save its result to db without LiveData. LiveData in onCreate method will be triggered.

   @Override


  protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);

  ViewModel viewModel = ViewModelProviders.of(this, factory).get(ViewModel.class);
            viewModel.getEntity().observe(this, entity -> {
                if (entity != null) {
                    adapter.notifyDataSetChanged(entity );

                }
            });

    requestQueue = Volley.newRequestQueue(this);

    Intent intent = getIntent();
    String searchText = intent.getStringExtra("searchText");

    // Three concurrent API GET Calls
    getMediumData(searchText);
    getExampleData(searchText);
    getGoogleData(searchText);

    recyclerView = findViewById(R.id.staggered_recycler_view);
    staggeredGridLayoutManager = new StaggeredGridLayoutManager(2, LinearLayoutManager.VERTICAL);
    recyclerView.setLayoutManager(staggeredGridLayoutManager);
}

ArrayList<StaggeredCustomCard> dataset = new ArrayList<>();

private void getMediumData(String searchText) {
    progressBar = findViewById(R.id.progressBar);
    progressBar.setVisibility(View.VISIBLE);

    String url = UrlConstants.getUrl() + searchText;

    JsonObjectRequest jsonObjectRequest = new JsonObjectRequest
            (Request.Method.GET, url, null, new Response.Listener<JSONObject>() {



                   @Override
                    public void onResponse(JSONObject response) {
                        StaggeredCustomCardDAO.insert();
                        // TODO just insert to dataBase
                    }

                }, new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        // TODO: Handle error
                        progressBar.setVisibility(View.INVISIBLE);
                    }
                });

        jsonObjectRequest.setRetryPolicy(new

                DefaultRetryPolicy(
                DefaultRetryPolicy.DEFAULT_TIMEOUT_MS * 15,
                DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
                DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
        requestQueue.add(jsonObjectRequest);
    }

    private void getExampleData(String searchText) {


        JsonArrayRequest jsonArrayRequest = new JsonArrayRequest
                (Request.Method.GET, url, null, new Response.Listener<JSONArray>() {

                    @Override
                    public void onResponse(JSONArray response) {
                        try {

                            // TODO just insert to dataBase
                          StaggeredCustomCardDAO.insert();
                        } catch (
                                JSONException e) {
                            e.printStackTrace();
                        }
                    }
                }, new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {

                    }
                });

        jsonArrayRequest.setRetryPolicy(new DefaultRetryPolicy(
                DefaultRetryPolicy.DEFAULT_TIMEOUT_MS * 15,
                DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
                DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
        requestQueue.add(jsonArrayRequest);
    }

    private void getGoogleData(String searchText) {


        JsonObjectRequest jsonObjectRequest = new JsonObjectRequest
                (Request.Method.GET, url, null, new Response.Listener<JSONObject>() {

                    @Override
                    public void onResponse(JSONObject response) {

                        // TODO just insert to dataBase
StaggeredCustomCardDAO.insert();
                    }
                }, new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {

                    }
                });

        jsonObjectRequest.setRetryPolicy(new DefaultRetryPolicy(
                DefaultRetryPolicy.DEFAULT_TIMEOUT_MS * 15,
                DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
                DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
        requestQueue.add(jsonObjectRequest);


    }


    class ViewModel extends androidx.lifecycle.ViewModel {


        private LiveData<StaggeredCustomCard> entity;


        public ViewModel(Repository repository) {
            entity = repository.getNetworkData();
        }


        public LiveData<StaggeredCustomCard> getEntity() {
            return entity;
        }
    }

    class Repository {

        LiveData<StaggeredCustomCard> getNetworkData() {

            LiveData<StaggeredCustomCard> localeData =  StaggeredCustomCardDAO .getLocaleData();//... todo Read from data base
            return localeData;
        }
    @Dao
    public interface StaggeredCustomCardDAO {
        @Query("Select * from tbl_staggeredCustomCard ")
        LiveData<StaggeredCustomCard> getLocaleData();

        @Insert(onConflict = OnConflictStrategy.REPLACE)
        void insert(List<StaggeredCustomCard> items);

    }
    }
Finicking answered 5/10, 2019 at 19:51 Comment(0)
S
0

The data should not disappear due to that error. First thing first I would suggest you to check maybe you clear your dataset somewhere or your screen is getting recreated.

Secondly I would recommend you to move from Volley since it is quite outdated from modern techniques library that requires a lot of attention to details. Use Retrofit - it is modern, powerful and community approved library. Using it you will be sure that all the requests you want to be async are async and all the responses are handled well.

Also I would suggest you not to create adapter each time you load data but use one adapter and add data to it and them use notifyDataSetChanged() to reload list with relevant data.

Sera answered 3/10, 2019 at 8:57 Comment(0)
A
0

First way : firstly i would suggest you to make a central request queue.

public class AppController extends Application {
    public static final String TAG = AppController.class
            .getSimpleName();
    private RequestQueue mRequestQueue;

    private static AppController mInstance;

    @Override
    public void onCreate() {
        super.onCreate();
        mInstance = this;
    }

    public static synchronized AppController getInstance() {
        return mInstance;
    }

    public RequestQueue getRequestQueue() {
        if (mRequestQueue == null) {
            mRequestQueue = Volley.newRequestQueue(getApplicationContext());
        }

        return mRequestQueue;
    }

    public <T> void addToRequestQueue(Request<T> req, String tag) {
        // set the default tag if tag is empty
        req.setTag(TextUtils.isEmpty(tag) ? TAG : tag);
        getRequestQueue().add(req);
    }

    public <T> void addToRequestQueue(Request<T> req) {
        req.setTag(TAG);
        getRequestQueue().add(req);
    }

    public void cancelPendingRequests(Object tag) {
        if (mRequestQueue != null) {
            mRequestQueue.cancelAll(tag);
        }
    }
}

then add your desired requests to the queue

// Adding request to request queue
AppController.getInstance().addToRequestQueue(jsonObjReq);

Second way : Create a Generic Volley class and a Interface, Use the interface to get success and failure responds.

Step 1 Create a separate Volley class Step 2 Create a interface for accessing the response from volley class Step 3 create new object for the class and send required parameters new PostVolleyJsonRequest(TestVolley.this, TestVolley.this(interfcae), "Submit", url, params);

Context of the class Interface for sending Success and failure responds Type of request to identify on success url (mandatory) Param (optional) for GET no need Generic volley class

public class PostVolleyJsonRequest {
private String  type;
private Activity act;
private VolleyJsonRespondsListener volleyJsonRespondsListener;
private String networkurl;
private JSONObject jsonObject = null;
private JSONObject params;


public PostVolleyJsonRequest(Activity act, VolleyJsonRespondsListener volleyJsonRespondsListener, String type, String netnetworkUrl,JSONObject params) {
    this.act = act;
    this.volleyJsonRespondsListener = volleyJsonRespondsListener;
    this.type = type;
    this.networkurl = netnetworkUrl;
    this.params = params;
    sendRequest();
}

private void sendRequest() {

    Log.d("url", "url" + networkurl);
    JsonObjectRequest jsObjRequest = new JsonObjectRequest(Request.Method.POST,networkurl,params,
            new Response.Listener<JSONObject>() {
                @Override
                public void onResponse(JSONObject response) {
                    Log.e("response", "response " + response);
                    volleyJsonRespondsListener.onSuccessJson(response, type);
                }
            },
            new Response.ErrorListener() {
                @Override
                public void onErrorResponse(VolleyError error) {
                    try {
                        NetworkResponse response = error.networkResponse;
                        Log.e("response", "response " + response);
                        if (response != null) {
                            int code = response.statusCode;

                            String errorMsg = new String(response.data);
                            Log.e("response", "response" + errorMsg);
                            try {
                                jsonObject = new JSONObject(errorMsg);
                            } catch (JSONException e) {
                                e.printStackTrace();
                            }
                            String msg = jsonObject.optString("message");
                            volleyJsonRespondsListener.onFailureJson(code, msg);
                        } else {
                            String errorMsg = error.getMessage();
                            volleyJsonRespondsListener.onFailureJson(0, errorMsg);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });

    jsObjRequest.setRetryPolicy(new DefaultRetryPolicy(
            600000,
            DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
            DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));

    RequestQueue requestqueue = Volley.newRequestQueue(act);
    requestqueue.add(jsObjRequest);

}
}

Use the interface to get responds message

public interface VolleyJsonRespondsListener {

public void onSuccessJson(JSONObject result, String type);
public void onFailureJson(int responseCode, String responseMessage);
}

In your class where you want to include multiple request

public class TestVolley extends AppCompatActivity implements VolleyJsonRespondsListener{

//Your class code goes here


//network request

try {
        //parameters 
        //Context,Interface,Type(to indentify your responds),URL,parameter for your request 

        //request 1
        new PostVolleyJsonRequest(TestVolley.this, TestVolley.this, "Submit", url, params);

        //request 2
        new PostVolleyJsonRequest(TestVolley.this, TestVolley.this, "AccessData", url_2, params_2);




 } catch (Exception e) {

 e.printStackTrace()
 }

 //Methods from Interface

  @Override
public void onSuccessJson(JSONObject result, String type) {

   //Based on the Type you send get the responds and parse it 
    switch (type) {
        case "Submit":
            try {
                parseSubmit(result);
            } catch (Exception e) {
Al answered 6/10, 2019 at 6:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.