Volley: Create a method that takes care of both JSONArrayRequest and JSONObjectRequest - java

Newbie here.
I'm working on a simple client-server Android app with my friend, trying my best to write the cleanest most beautiful code that I can, strictly following the SOLID principles and using Design Patterns to decouple my classes and components. I wrote this method, hoping that it'll be the only networking code I'll have to write:
private void VolleyJSONPostParserRequest(String url, JSONObject requestBody,
final Handler handler, final IParser replyParser) {
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest
(Request.Method.POST, url, requestBody, new Response.Listener<JSONObject>() {
#Override
public void onResponse(JSONObject response) {
try {
Object parsedResponse = replyParser.parse(response.toString());
notifyObservers(handler, parsedResponse);
} catch (Exception e) {
Log.e(TAG,handler.getClassName() + " reply parsing error!");
notifyObservers(handler, null); }
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Log.e(TAG,"Error receiving response for " + handler.getClassName());
notifyObservers(handler, null);
}
});
}
To my discontent, I found out that sometimes the server's reply will be a JSONObject, and sometimes it will be a JSONArray. I do not want to copy-paste the entire thing and replace "JsonObjectRequest" with "JsonArrayRequest". What's a good solution for this problem? Or is it one of those cases where I'd be better off just copying and pasting?

In my opinion, you should implement a custom request (for example, public class CustomRequest extends Request<NetworkResponse>). Please read the following Google's training doc for more information:
Google Volley - Implementing a Custom Request
then...
#Override
public void onResponse(NetworkResponse response) {
try {
final String jsonString = new String(response.data,
HttpHeaderParser.parseCharset(response.headers));
// Check if it is JSONObject or JSONArray
Object json = new JSONTokener(jsonString).nextValue();
if (json instanceof JSONObject) {
//do something...
} else if (json instanceof JSONArray) {
//do something...
} else {
//do something...
}
...
} catch (UnsupportedEncodingException | JSONException e) {
e.printStackTrace();
}
}

Related

JSONArray needs higher API level, but is used in same class without issue

This is a reoccurring issues that I keep getting in Android Studio. I will go to use the JSONArray class, and it will tell me that "Call Requires API Level 19(current min is 15)". The weird thing is that I am able to use JSONArray in other spots in the same class.
Some things that I've tried are cleaning and building the project (does nothing), restarting Android Studio (also does nothing for the issue), and rewriting the method that uses JSONArray, or move it (This will sometimes work)
Here is an example where I don't get the issue
private void makeApiCall(String text) {
APICall.make(context, text, new Response.Listener<String>() {
#Override
public void onResponse(String response) {
List<Employee> employees = new ArrayList<>();
try {
JSONArray array = new JSONArray(response);
employees = new APIHelper().populateEmployeetList(array);
} catch (Exception e) {
e.printStackTrace();
}
autoAdapter.setData(employees);
autoAdapter.notifyDataSetChanged();
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
}
});
}
And here is an example where I get the Error
private void getJobs(String text){
APICall.getJobsByPartial(context, text, new Response.Listener() {
#Override
public void onResponse(Object response) {
try{
List<Function> functions = new ArrayList<>();
JSONArray jobs = new JSONArray(response);
functions = new APIHelper().populateFunctionList(jobs);
jobAdapter.setData(functions);
jobAdapter.notifyDataSetChanged();
}
catch (JSONException ex){
ex.printStackTrace();
}
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
}
});
}
In your first code snippet, response is a String. The JSONArray constructor that takes a String has been around since API Level 1.
In your second code snippet, response is an Object. The JSONArray constructor that takes an Object has only been around since API Level 19.
In your second code snippet, APICall.getJobsByPartial() should be giving your callback something more specific than an Object.

How Receieve Validation Errors In Java Android Spring

I am sending API requests to a backend API using Spring in Android (Java). My question is how to receive validation errors to the error handler at ex 400 bad request response. Here is my code:
class RestTask extends AsyncTask<String,Void,ResponseEntity<ExpectedReturn>>
{
protected ResponseEntity<ExpectedReturn> doInBackground(String... uri)
{
try{
final String url = uri[0];
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(subscriber.getErrorHandler());
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
// set authentication tokens:
ResponseEntity<ExpectedReturn> response = restTemplate.exchange(url,callMethod,httpEntity, expectedReturnClass);
return response;
}catch(Exception e)
{
System.out.println(e.getMessage());
}
return null;
}
#Override
protected void onPostExecute(ResponseEntity<ExpectedReturn> result) {
if(result !=null && result.getBody() !=null)
{
subscriber.getSubscriber().onComplete(result.getBody(),result.getStatusCode());
}
}
}
My question is, if the post data fails validation (is incorrect), the API will return as JSON error object with errors, ex:
In case of a validation error, the error handler is called with a ClientHttpResponse object as a parameter. Calling the response.getBody() returns an InputStream. My question is, is there any way of receiving an object mapped from the JSON error response (as shown above) to the error handler, or perhaps converting the input stream to something readable (like a hashmap) so I can display the errors returned by the API (ex: "Name is required" etc...)?
I've tested your code and in case of a 400 bad request the catch block receives an instance of HttpClientErrorException which has a method to get the error body as String:
private class HttpRequestTask extends AsyncTask<Void, Void, String> {
#Override
protected String doInBackground(Void... params) {
try {
final String url = "https://reqres.in/api/login";
RestTemplate restTemplate = new RestTemplate();
//Same result with restTemplate.exchange() too
return restTemplate.postForObject(url, "{\n" +
" \"email\": \"peter#klaven\"\n" +
"}", String.class);
} catch (Exception e) {
Log.e(TAG, e.getMessage());
if (e instanceof HttpClientErrorException) {
String responseBodyAsString = ((HttpClientErrorException) e).getResponseBodyAsString();
Log.e(TAG, "Validation error" + responseBodyAsString);
//You can parse this with gson or jackson here
return responseBodyAsString;
}
}
return null;
}
#Override
protected void onPostExecute(String result) {
Log.d(TAG, "onPostExecute() called with: result = [" + result + "]");
}
}
Which prints in:
W/RestTemplate: POST request for "https://reqres.in/api/login" resulted in
400 (Bad Request); invoking error handler
E/MainActivity: 400 Bad Request
E/MainActivity: Validation error{"error":"Missing email or username"}
D/MainActivity: onPostExecute() called with: result = [{"error":"Missing email or username"}]
If you want to use the none default error handler and set your custom error handler you can get the error message as string this way:
restTemplate.setErrorHandler(new ResponseErrorHandler() {
#Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return response.getStatusCode().is4xxClientError();
}
#Override
public void handleError(ClientHttpResponse response) throws IOException {
String errorResponse = new String(getResponseBody(response), getCharset(response).name());
Log.e(TAG, "handleError: called with: " + errorResponse);
}
});
private byte[] getResponseBody(ClientHttpResponse response) {
try {
InputStream responseBody = response.getBody();
if (responseBody != null) {
return FileCopyUtils.copyToByteArray(responseBody);
}
} catch (IOException ex) {
// ignore
}
return new byte[0];
}
private Charset getCharset(ClientHttpResponse response) {
HttpHeaders headers = response.getHeaders();
MediaType contentType = headers.getContentType();
return contentType != null ? contentType.getCharSet() : Charset.defaultCharset();
}
Then you can use Jackson or Gson to parse the error response as below:
new Gson().fromJson(responseBodyAsString, ExpectedResponse.class);
Note I've just did the same thing as implemented in DefaultResponseErrorHandler
Edit:
The whole AsyncTask and Spring Android APIs are so outdated, Here is the same example with Retrofit:
api.login(new BodyModel("peter#klaven"))
.enqueue(new Callback<ExpectedModel>() {
#Override
public void onResponse(#NonNull Call<ExpectedModel> call, #NonNull Response<ExpectedModel> response) {
if (response.isSuccessful()) {
//Do what you got to do
} else {
Converter<ResponseBody, ErrorModel> converter = MainActivity.this.retrofit.responseBodyConverter(ErrorModel.class, new Annotation[0]);
ErrorModel errorModel = null;
try {
errorModel = converter.convert(response.errorBody());
Toast.makeText(MainActivity.this, errorModel.toString(), Toast.LENGTH_SHORT).show();
} catch (IOException e) {
e.printStackTrace();
}
}
}
#Override
public void onFailure(#NonNull Call<ExpectedModel> call, #NonNull Throwable t) {
t.printStackTrace();
}
})
You can find the full gist in my github repo

when sending post request with codeName one, Json String is empty

I am trying to send a POST request with a JSON BODY in CodeName one.
It reaches the server with an empty Json String.
Here is the code that makes the connection and sends the message:
JSONObject json = new JSONObject();
class MyConnection extends ConnectionRequest {
public Map<String, Object> results;
#Override
protected void readResponse(InputStream input) throws IOException {
JSONParser jp = new JSONParser();
results = jp.parseJSON(new InputStreamReader(input, "UTF-8"));
}
#Override
protected void handleErrorResponseCode(int code, String message) {
showError("The server returned the error code: " + code);
}
#Override
protected void handleException(Exception err) {
showError("There was a connection error: " + err);
}
#Override
protected void postResponse() {
try {
json.put("AAA", "AAA");
json.put("BBB", "BBB");
json.put("CCC", "CCC");
} catch (JSONException ex) {
ex.printStackTrace();
}
}
#Override
protected void buildRequestBody(OutputStream os) throws IOException {
os.write(json.toString().getBytes("UTF-8"));
}
}
---
MyConnection connec = new MyConnection ();
connec.setUrl("http://testServoce/addUser");
connec.setPost(true);
connec.setContentType("application/json");
InfiniteProgress prog = new InfiniteProgress();
Dialog dlg = prog.showInifiniteBlocking();
connec.setDisposeOnCompletion(dlg);
NetworkManager.getInstance().addToQueueAndWait(connec);
I'm not sure what your intention was but it looks like you misunderstood the goal of the postResponse method. It's unrelated to the POST web method and is just called after the response completed on the EDT. So changing the JSON value there is irrelevant.
Also it looks like you are using two separate JSON parsers for some reason. The builtin one and the org.json one from one of the cn1libs.

How do I reference my JSON onResponse in my postexecute method?

I am trying to create an async task for my JsonArray, as it is skipping frames and lagging. I created an asyncTask class put in the parameters and i called my Json request in the do in background , however, how do I now reference my Json onresponse in my postexecute method?
class MyTask extends AsyncTask<URL,String,JSONArray> {
#Override
protected JSONArray doInBackground(URL... params) {
JsonRequestMethod();
return null;
}
#Override
protected void onPostExecute(JSONArray response) {
}
}
}
private void JsonRequestMethod() {
mVolleySingleton = VolleySingleton.getInstance();
//intitalize Volley Singleton request key
mRequestQueue = mVolleySingleton.getRequestQueue();
//2 types of requests an Array request and an Object Request
JsonArrayRequest request = new JsonArrayRequest(Request.Method.GET, URL_API, (String) null, new Response.Listener<JSONArray>() {
#Override
public void onResponse(JSONArray response) {
listblogs.clear();
listblogs=parseJSONResponse(response);
mAdapterDashBoard.setBloglist(listblogs);
System.out.println("it worked!!!");
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
}
});
mRequestQueue.add(request);
}
private ArrayList<Blogs> parseJSONResponse(JSONArray response) {
if (!response.equals("")) {
ArrayList<Blogs> blogsArrayList = new ArrayList<>();
try {
StringBuilder data = new StringBuilder();
for (int i = 0; i < response.length(); i++) {
JSONObject currentQuestions = response.getJSONObject(i);
String text = currentQuestions.getString("text");
String points = currentQuestions.getString("points");
String ID=currentQuestions.getString("id");
String studentId = currentQuestions.getString("studentId");
String DateCreated=currentQuestions.getString("created");
long time=Long.parseLong(DateCreated.trim());
data.append(text + "\n" + points + "\n");
System.out.println(data);
Blogs blogs = new Blogs();
blogs.setId(ID);
blogs.setMstudentId(studentId);
blogs.setMtext(text);
blogs.setPoints(points);
//The dateCreated was off by 1 hour so 3600000 ms where added=1hour, (UPDATE)
blogs.setDateCreated(getTimeAgo(time));
System.out.println(time+"time");
listblogs.add(blogs);
}
} catch (JSONException e) {
e.printStackTrace();
}
}
return listblogs;
}
Your volley request is already asynchronous, that's why it has listener interfaces like onResponse() and onErrorResponse(). You don't need it in the AsyncTask at all. Call your volley request from the UI thread, and use your AsyncTask to parse the JSON. For example:
Call your volley request.
In onResponse() from your volley request, create your AsyncTask to handle parsing.
Parse the JSON in your doInBackground() method, "return" the result of the parsing.
Your onPostExecute() will receive the parsing "result," and you can do what you want with it there.
To go even further, you could implement a listener interface for your AsyncTask as well, something like onJSONParsed(), then you could set logic in your UI thread (where you called your volley request), to handle the 100% finished response. For example:
class MyTask extends AsyncTask<URL,String,ArrayList<Blogs>> {
// Listener member variable.
private OnJSONParsedListener mOnJSONParsedListener;
#Override
protected ArrayList<Blog> doInBackground(URL... params) {
// Parse JSON.
ArrayList<Blogs> blogsList = parseJSON(response);
// Pass the blogs list to the onPostExecute method.
return blogsList;
}
#Override
protected void onPostExecute(ArrayList<Blogs> blogsList) {
// Invoke listener, if present.
if (mOnJSONParsedListener != null)
mOnJSONParsedListener.onJSONParsed(blogsList);
}
// Listener interface.
public interface OnJSONParsedListener {
void onJSONParsed(ArrayList<Blogs> blogsList);
}
// Setter for listener interface member variable.
public void setOnJSONParsedListener(OnJSONParsedListener listener) {
mOnJSONParsedListener = listener;
}
}
Then in your UI thread.
// Call your volley request.
mVolleySingleton = VolleySingleton.getInstance();
//intitalize Volley Singleton request key
mRequestQueue = mVolleySingleton.getRequestQueue();
//2 types of requests an Array request and an Object Request
JsonArrayRequest request = new JsonArrayRequest(Request.Method.GET, URL_API, (String) null, new Response.Listener<JSONArray>() {
#Override
public void onResponse(JSONArray response) {
// Create AsyncTask.
MyTask parseJSONTask = new MyTask();
// Set listener interface.
parseJSONTask.setOnJSONParsedListener(new MyTask.OnJSONParsedListener() {
#Override
public void onJSONParsed(ArrayList<Blogs> blogsList) {
// Do stuff with your blogs list.
mAdapterDashBoard.setBloglist(blogsList);
// It worked!
System.out.println("it worked!!!");
}
}
// Execute.
parseJSONTask.execute(response);
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
}
});
mRequestQueue.add(request);
Hope this helps.

java work around to access a variable from anonymous inner class not working

Sorry this is a repeated question ,
though referring to this post. I have tried to assign a value from anonymous inner class. But it always prints null[Toast.makeText(getBaseContext(),token[0],Toast.LENGTH_SHORT).show();] . Where i am doing wrong in this code sample.
Is there a better way to return the string value than this.?
public String getAccessToken(String url) {
final String[] token = new String[1];
JsonObjectRequest postRequest = new JsonObjectRequest(Request.Method.POST, url,
new Response.Listener<JSONObject>()
{
#Override
public void onResponse(JSONObject response) {
try {
token[0] = response.getString("access_token");
tv.setText(token[0]);
} catch (JSONException e) {
e.printStackTrace();
}
}
},
new Response.ErrorListener()
{
#Override
public void onErrorResponse(VolleyError error) {
// error
Log.d("Error.Response", String.valueOf(error));
}
}
);
queue.add(postRequest);
Toast.makeText(getBaseContext(),token[0],Toast.LENGTH_SHORT).show();
return token[0];
}
You're basically returning token[0] before you assign anything to it. The way that method works is like this:
You create token[0] (which is null) at ths point.
You send the request.
You return token[0] (still null at this point)
You get the response from the request, which has the value you initially wanted for token[0].
Unless you get the response back, token[0] will be null. You won't be able to return it from that method. Instead I'd just make it void and wait for the request to finish. You can Toast it from onResponse if you wish.
public void getAccessToken(String url) {
JsonObjectRequest postRequest = new JsonObjectRequest(Request.Method.POST, url,
new Response.Listener<JSONObject>()
{
#Override
public void onResponse(JSONObject response) {
try {
token[0] = response.getString("access_token");
tv.setText(token[0]);
// do some other stuff with token[0], like toast or whatever
} catch (JSONException e) {
e.printStackTrace();
}
}
},
new Response.ErrorListener()
{
#Override
public void onErrorResponse(VolleyError error) {
// error
Log.d("Error.Response", String.valueOf(error));
}
}
);
queue.add(postRequest);
}

Categories