I'm very new at programming for Android - please bear with me.
I'm building an app that requires network access, using OKHttp. Since I will be making many similarly structured requests from my server, I created a class that handles all network-related tasks, as I like to keep things compartmentalized.
One method I'm working on is createNetworkThread from within my NetworkManager class. This particular method takes three arguments:
Context context, final String requestURI, final RequestBody formParameters
What I need assistance with is how to return the data received from this method so I can use and manipulate it in the calling Activity.
Here is the method in question:
public void createNetworkThread(Context context, final String requestURI, final RequestBody formParameters) {
if (!this.isConnected(context)) return;
Runnable runnable = new Runnable() {
#Override
public void run() {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(requestURI).post(formParameters).build();
Response response = null;
// Send login request, get response //
try {
response = client.newCall(request).execute();
String stringResponse = response.body().string();
JSONObject jsonResponse = new JSONObject(stringResponse);
Log.d("Net", "Request send and received!");
} catch (IOException e) {
Log.d("Net", "Failed");
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
}
};
Thread thread = new Thread(runnable);
thread.start();
}
Here is the call from the Activity:
final NetworkManager Net = new NetworkManager(this);
...
final String requestURI = "http://192.168.1.111/videonow.club/apprequest/signup/thread.php";
final RequestBody formVars = new FormBody.Builder().add("email", strEmail).add("password", strPass1).add("first_name", strNameFirst).add("last_name", strNameLast).build();
Net.createNetworkThread(SignupActivity.this, requestURI, formVars);
What I need to know is how to get the JSON data from jsonResponse returned from the method (I know void doesn't allow this) so I can use the data.
Would it be better to have the jsonObject returned so I can use something like this:
SomeType response = Net.createNetworkThread(...);
Or, to have a class variable within NetworkManager that would be set by the method so it would be called to and referenced like this:
Net.createNetworkThread(...);
SomeType response = Net.someVariable;
Or is there some much more reasonable way to receive this data?
I'm also calling new OkHttpClient() twice - once in the activity, so I can build the requestBody post variables, as well as in the NetworkManager class itself. My instincts tell me this is redundant... if so, is there a way to make this more efficient?
Thanks in advance!
You can use OkHttp with AysncTask like this:
public class Webservice extends AsyncTask<String, String, UserResponse> {
private String TAG = Webservice.class.getSimpleName();
private static final String ENDPOINT = "YOUR_URL";
private static final Moshi MOSHI = new Moshi.Builder().build();
private static final JsonAdapter<UserResponse> CONTRIBUTORS_JSON_ADAPTER_RESPONSE = MOSHI.adapter(Types.newParameterizedType(UserResponse.class, UserResponse.class));
UserResponse webResponse;
public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
String postBody = "postBody\n";
#Override
protected UserResponse doInBackground(String... parmas) {
OkHttpClient.Builder okHttpClient = new OkHttpClient.Builder();
Call call = okHttpClient.build().newCall(new Request.Builder()
.url(ENDPOINT)
.post(RequestBody.create(JSON, postBody))
.addHeader("Content-Type", "application/json")
.build());
try {
Response response = call.execute();
adModelResponse = CONTRIBUTORS_JSON_ADAPTER_RESPONSE.fromJson(response.body().source());
} catch (IOException e) {
e.printStackTrace();
}
return webResponse;
}
#Override
protected void onPostExecute(UserResponse adModelResponse) {
}
}
And then in Activity call like this:
Webservice webservice = new Webservice();
webservice.execute("YOUR_PARAMETER");
Libraries Used:
okhttp-3.2.0, moshi-1.1.0, okio-1.8.0
Make NetworkManager Abstract and add one abstract method say public abstract void onResult(JSONObject response); and override this method like
final NetworkManager Net = new NetworkManager(this){
#Override
public void onResult(JSONObject response){
//do whatever you want here
}
};
And from the createNetworkThread when finished call this method as
.....
response = client.newCall(request).execute();
String stringResponse = response.body().string();
JSONObject jsonResponse = new JSONObject(stringResponse);
onResult(jsonResponse);
......
You can use callback interface to get your data back to your activity. Consider the example below:
public interface JsonResponse {
onResponseReceived(JSONObject response);
}
Then your createNetworkThread will looks like this:
public void createNetworkThread(Context context, final String requestURI, final RequestBody formParameters, JsonResponse responseCallback) {
if (!this.isConnected(context)) return;
Runnable runnable = new Runnable() {
#Override
public void run() {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(requestURI).post(formParameters).build();
Response response = null;
// Send login request, get response //
try {
response = client.newCall(request).execute();
String stringResponse = response.body().string();
JSONObject jsonResponse = new JSONObject(stringResponse);
responseCallback.onResponseReceived(jsonResponse); // This line will return to your caller
Log.d("Net", "Request send and received!");
} catch (IOException e) {
Log.d("Net", "Failed");
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
}
};
Thread thread = new Thread(runnable);
thread.start();
}
And finally the caller:
Net.createNetworkThread(SignupActivity.this, requestURI, formVars, new JsonResponse() {
#Override
public void onResponseReceived(JSONObject response) {
// Do stuff in your activity
// eventually use runOnUiThread for your UI operations
}
});
Related
I have a async class in my MainActivity.java
class Register extends AsyncTask<String, String, JSONObject> {
JSONObject json;
#Override
protected JSONObject doInBackground(String[] args) {
String function = args[3];
String email = args[2];
String password = args[1];
String name = args[0];
ContentValues params = new ContentValues();
params.put("username", name);
params.put("password", password);
params.put("function", function);
if (email.length() > 0)
params.put("email", email);
String URL = "https://lamp.ms.wits.ac.za/home/s2090704/index.php";
new PhpHandler().makeHttpRequest(act, URL, params, new RequestHandler() {
#Override
public void processRequest(String response) throws JSONException {
json = new JSONObject(response);
System.out.println(json); //outputs {response: " ...",message:"..."}
}
});
System.out.println(json); //outputs null
return json;
}
}
in doInBackground() PhpHandler processes details using OkHttp.
public class PhpHandler {
JSONObject json;
static String responseData = "";
public void makeHttpRequest(final Activity a, String url,
ContentValues params, final RequestHandler rh) {
// Making HTTP request
OkHttpClient client = new OkHttpClient();
FormBody.Builder builder = new FormBody.Builder();
for (String key : params.keySet()) {
builder.add(key, params.getAsString(key));
}
final Request request = new Request.Builder()
.url(url)
.post(builder.build())
.build();
client.newCall(request).enqueue(new Callback() {
#Override
public void onFailure(#NotNull Call call, #NotNull IOException e) {
}
#Override
public void onResponse(#NotNull Call call, #NotNull Response response) throws IOException {
responseData = Objects.requireNonNull(response.body()).string();
//System.out.println(responseData);
a.runOnUiThread(new Runnable() {
#Override
public void run() {
try {
rh.processRequest(responseData);
} catch (JSONException e) {
e.printStackTrace();
}
}
});
}
});
}
}
RequestHandler is an interface that processes request on the mainUiThread.
package com.example.registration;
import org.json.JSONException;
public interface RequestHandler{
void processRequest(String response) throws JSONException;
}
Now json doesn't update out of the processRequest method in doInBackground method of my async class Register.I know that interfaces make variables static and final is there any way to update the value of json?
processRequest method will be executed long after you return json from doInBackground, because makeHttpRequest performs an asynchronous http request.
Knowing this, you will probably want to re-design this class (there is no need to wrap already asynchronous request in AsyncTask), but if you really want to do it this way, you would have to wait for your request to complete before returning the json (for example, by using CountDownLatch).
CountDownLatch latch = new CountDownLatch(1);
someField = null;
AtomicReference<String> someValue = new AtomicReference<>();
// don't start new threads like this, im just trying to keep this example simple
new Thread() {
Thread.sleep(1000); // sleep for 1 second
someValue.set("abc"); // notice that because when using AtomicReference you assign it's value using `set` method instead of `=` operator, you can keep it as local variable instead of field class
latch.countDown(); // reduce latch count by one
}.run();
System.out.println(someValue.get()); // null - because the assignation will happen in one second
latch.await(); // this will force current thread to wait until the latch count reaches zero (initial was 1, passed to constructor)
System.out.println(someValue.get()); // "abc"
read more
I have an asyncTask in my application as below. I have to fetch data from realm(which is successfully stored in another activity) into this AsyncTask. Below is my AsyncTask code:
public class MakeAsyncRequest extends AsyncTask<Object, String, MakeAsyncRequest.ResponseDataType>{
public Context asyncContext;
private MakeAsyncRequest(Context context){
asyncContext = context;
}
private static final OkHttpClient client = new OkHttpClient();
private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
private MakeRequest.ResponseHandler handler;
private String method;
private User user;
private Realm realm;
class ResponseDataType
{
InputStream inputStream;
String string;
String cookie;
}
MakeAsyncRequest(MakeRequest.ResponseHandler responseHandler, String type)
{
method = type;
handler = responseHandler;
}
#Override
protected ResponseDataType doInBackground(Object... params) {
try {
Requests requests = new Requests((Context) params[0]);
String url = params[1].toString();
String bodyJson = null;
if(method.equals("PUT") || method.equals("POST")) {
bodyJson = params[2].toString();
}
final Request.Builder builder;
Response response;
RequestBody body;
switch (method) {
case "GET": builder = new Request.Builder().url(url);
break;
case "DOWNLOAD": builder = new Request.Builder().url(url);
break;
case "POST": body = RequestBody.create(JSON, bodyJson);
builder = new Request.Builder()
.url(url)
.post(body)
.addHeader("Cookie", "key=value");
break;
case "PUT": body = RequestBody.create(JSON, bodyJson);
builder = new Request.Builder()
.url(url)
.put(body);
break;
default: builder = new Request.Builder().url(url);
}
builder.addHeader("Accept", "application/json");
realm = RealmController.initialize(this).getRealm();
final RealmResults<User> users = realm.where(User.class).findAllAsync();
user = users.first();
if(user.getCookie() !== null && !user.getCookie().isEmpty()){
builder.addHeader("cookie", "user.getCookie()");
}
response = client.newCall(builder.build()).execute();
ResponseDataType responseDataType = new ResponseDataType();
if(method.equals("DOWNLOAD")) { responseDataType.inputStream = response.body().byteStream(); }
else {
responseDataType.string = response.body().string();
responseDataType.cookie = response.headers().get("set-cookie");
CookieManager cManager = new CookieManager(null, CookiePolicy.ACCEPT_ALL);
cManager.getCookieStore().getCookies();
}
return responseDataType;
} catch (IOException e) {
return null;
}
}
#Override
protected void onPostExecute(ResponseDataType response) {
try {
if (method.equals("DOWNLOAD")) {
handler.onFinishCallback(response.inputStream);
}else {
handler.onFinishCallback(response.string, response.cookie);
CookieManager cManager = new CookieManager(null, CookiePolicy.ACCEPT_ALL);
cManager.getCookieStore().getCookies();
}
}catch (Exception e){
Log.d("hExceptionError", e.toString());
handler.onFinishCallback("{\n" +
" \"error\": \"error\"\n" +
"}","");
}
}
I am received the access error - "Realm access from incorrect thread. Realm objects can only be accessed on the thread they were created." whenever the control tries to execute the Realm results or to get the first object from Realm.
Below is my RealmController which i created to control the realm instance:
public class RealmController {`public static RealmController initialize(Activity activity) {
if (instance == null) {
instance = new RealmController(activity.getApplication());
}
return instance;
}
public static RealmController initialize(MakeAsyncRequest activity) {
if (instance == null) {
instance = new RealmController(activity.);
}
return instance;
}
}`
I have my user model(Realm object) with setters and getters.
The point is - you cannot create and access Realm on different threads, i.e. create Realm instance in Activity and use it in .doInBackground() method. Create and release Realm immediately before and after transaction.
There may be another issue - don't register quer observer on background thread in AsyncTask - it doesn't have Looper initialized - use main thread or HandlerThread.
Release realm after it is no longer needed (you didn't in your code), because Realm has limited number of instances.
You can initiate a Realm instance in the doInBackground method of your AsyncTask like this:
private class MyAsyncTask extends AsyncTask<Void, Void, Void> {
#Override
protected Void doInBackground(Void... voids) {
Realm realm = Realm.getDefaultInstance();
// Do your Realm work here
realm.close();
return null;
}
}
It's important to note that you should always open and close a Realm instance in the same thread. In this case, since the AsyncTask is running in a background thread, you can open and close the Realm instance within the doInBackground method.
I'm using Volley however I'm having some problems with the JSON parsed data most likely because volley doesn't implement something like AsyncTask's onPostExecute() and I'm getting some duplicated data on wrong list items.
Then I came across this: https://github.com/yakivmospan/volley-request-manager#custom-listener-implementation-
Has anyone use it? How can I add it to my current Volley code?
More details about my problem here Volley not sending correct data. How to implement an alternative to onPostExecute()?
UPDATE
As requested, some code. Here's a button that calls a method on another class that uses Volley to request some raw JSON data (NovaJSON) and then send the JSON to a parser class (NovaParser):
info.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
String instanceDetail = NovaJSON.shared().receiveDetail(getId());
Dialog dialog = new Dialog(v.getContext());
dialog.setContentView(R.layout.instances_info);
TextView image = (TextView) dialog.findViewById(R.id.imageInstance);
TextView flavor = (TextView) dialog.findViewById(R.id.flavorInstance);
dialog.setTitle(name.getText() + " Details");
if (instanceDetail != null) {
image.setText(" \u2022 image : " + NovaParser.shared().parseImages(instanceDetail));
flavor.setText(" \u2022 flavor : " + NovaParser.shared().parseFlavor(instanceDetail));
}
dialog.show();
}
});
This is the method that does the Volley request on the NovaJSON class:
public void getJSONdetail() {
final String authToken = getAuth();
String novaURL = getNova();
novaURL = novaURL+"/servers/"+id;
JsonObjectRequest getRequest = new JsonObjectRequest(Request.Method.GET, novaURL, null,
new Response.Listener<JSONObject>() {
#Override
public void onResponse(JSONObject response) {
Log.d("Nova on Response", response.toString());
setNovaJSONdetail(response.toString());
}
},
new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
VolleyLog.d("Nova on Error", "Error: " + error.getMessage());
setNovaJSONdetail(error.toString());
}
}
) {
public Map<String, String> getHeaders() throws AuthFailureError {
Map<String, String> params = new HashMap<String, String>();
params.put("X-Auth-Token", authToken);
params.put("User-Agent", "stackerz");
params.put("Accept", "application/json");
params.put("Content-Type", "application/json; charset=utf-8");
return params;
}
};
queue = VolleySingleton.getInstance(this).getRequestQueue();
queue.add(getRequest);
}
It then sends the JSON from the server as a string to be parsed using the following methods:
public static String parseImages(String imagesDetail){
ArrayList<HashMap<String, String>> imagesList = NovaParser.shared().getImagesList();
String temp = null;
JSONObject novaDetail = null;
try {
novaDetail = new JSONObject(imagesDetail);
JSONObject server = novaDetail.getJSONObject("server");
JSONObject image = server.getJSONObject("image");
if (imagesList !=null){
temp = image.getString("id");
for (Map<String,String> map : imagesList) {
if (map.containsValue(temp)) {
temp = map.get(NAME);
}
}
}
} catch (JSONException e) {
e.printStackTrace();
}
return temp;
}
public static String parseFlavor(String instanceDetail){
ArrayList<HashMap<String, String>> flavorList = NovaParser.shared().getFlavorList();
String temp = null;
JSONObject novaDetail = null;
try {
novaDetail = new JSONObject(instanceDetail);
JSONObject server = novaDetail.getJSONObject("server");
JSONObject flavor = server.getJSONObject("flavor");
if (flavorList !=null){
temp = flavor.getString("id");
for (Map<String,String> map : flavorList) {
if (map.containsValue(temp)) {
temp = map.get(NAME);
}
}
}
} catch (JSONException e) {
e.printStackTrace();
}
return temp;
}
When I press the button once the dialog is displayed with empty values. When I press it the second time I get the correct parsed data. Basically first time I click the button the instanceDetail string is null because Volley didn't finish doing its thing then I click the 2nd time it loads the values accordingly because it finally finished the 1st request.
I understand Volley is asynchronous, the requests happen in parallel and the responses sometimes are not immediate however I need some sort of progress bar or spinning wheel to give the user some feedback that the app is waiting for data. It could be done with AsyncTask however it doesn't seem to be possible with Volley.
I think your problem is not because of Volley.
Check the parameters you send and receive.
However if you need onPostExcecute you have Volley's callback:
Response.Listener<JSONObject> and Response.ErrorListener() which are called after the request.
About Volley request manager just switch all your volley calls with appropriate Volley request manager calls
I solved my problem by dumping Volley altogether and moving to Retrofit. I setup all the calls to be sync/blocking, worked out the exceptions/errors using try/catches and setup a short timeout on the OkHTTP client. Now it's working as I wanted.
I feel like this is a very basic concept missunderstanding. In my Android app I make HTTP requests using Volley lib. At first I made the requests through a JsonObjectRequest in the Activity code and worked right, but now I separated the request code in a class apart so I can call it from any Activity. The new class has a method that returns the requested JSONObject but any "json action" I do over the returned object ends in an error.
Here is the new class code, JSONRequests:
public class JSONRequests {
private JSONObject mResponse;
private String mURL = "https://myurl.com/";
public JSONObject getJSONObject(final String credentials, final String path) {
final JsonObjectRequest mRequest = new JsonObjectRequest(mURL.concat(path), null,
new Response.Listener<JSONObject>() {
#Override
public void onResponse(JSONObject response) {
mResponse = response;
try {
Log.d("RESPONSE", mResponse.getString("id")); // It works here
} catch (JSONException e) {
e.printStackTrace();
}
}
},
new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
}
}
) {
#Override
public Map<String, String> getHeaders() throws AuthFailureError {
HashMap<String, String> headers = new HashMap<String, String>();
headers.put("Authorization", "Basic " + credentials);
return headers;
}
};
MyApplication.getInstance().getRequestQueue().add(mRequest);
return mResponse;
}
}
And here is how I call getJSONObject from MyActivity:
JSONRequests mRequest = new JSONRequests();
JSONObject mInfo = mRequest.getJSONObject(mEncodedCredentials, mPath);
try {
Log.d("RESPONSE", mInfo.getString("id")); // This doesn't work
} catch (JSONException e) {
e.printStackTrace();
}
When in the Activity file, I used "response" as a JSONObject and it worked, but now separated it won't. I don't know if is a error with JSONObject or just that I'm not getting the returned object in the correct way. Thanks in advance.
Suppose I have this code:
public HttpResponse myFunction(...) {
final HttpResponse resp;
OnResponseCallback myCallback = new OnResponseCallback() {
public void onResponseReceived(HttpResponse response) {
resp = response;
}
};
// launch operation, result will be returned to myCallback.onResponseReceived()
// wait on a CountDownLatch until operation is finished
return resp;
}
Obviously I can not assign a value to resp from onResponseReceived because it is a final variable, BUT if it was not a final variable onResponseReceived could not see it.
Then, how can I assign a value to resp from onResponseReceived?
What I thought is to create a wrapper class for enclosing the resp object. The final object would be an instance of this wrapper class and I could assign the value to resp working on the object inside the final class (which is not final).
The code would be this one:
class ResponseWrapper {
HttpResponse resp = null;
}
public HttpResponse myFunction(...) {
final ResponseWrapper respWrap = new ResponseWrapper();
OnResponseCallback myCallback = new OnResponseCallback() {
public void onResponseReceived(HttpResponse response) {
respWrap.resp = response;
}
};
// launch operation, result will be returned to myCallback.onResponseReceived()
// wait on a CountDownLatch until operation is finished
return respWrap.resp;
}
What do you think about this solution?
java.util.concurrent.atomic.AtomicReference
Standard practice is to use a final AtomicReference, which you can set and get. This adds the benefit of thread safety as well :) As you mentioned, a CountDownLatch is helpful in waiting for completion.
Your solution is as valid as any other. Other popular choices include the one element array
final HttpResponse[] resp = new Response[1];
// In the callback
resp[0] = response;
// After the operation
return resp[0];
and the generic wrapper
public class Ref<T> {
public T value;
}
final Ref<HttpResponse> resp;
// In the callback
resp.value = response;
// After the operation
return resp.value;
You can combine the hand-back and the wait into one using a SynchronousQueue (exception handling omitted)
public HttpResponse myFunction(...) {
final Queue<HttpResponse> resp = new SynchronousQueue<HttpResponse>();
OnResponseCallback myCallback = new OnResponseCallback() {
public void onResponseReceived(HttpResponse response) {
resp.put(response);
}
};
return resp.take();
}
The change I would make would be to use an AtomicReference since this is obviously multi-threaded and you wouldn't have to write your own wrapper. Otherwise, seems reasonable to me.
You can make it mutable and final ;) The simplest approach is to use na array but an AtomicReference can also be used.
public HttpResponse myFunction(...) {
final HttpResponse[] resp = { null };
OnResponseCallback myCallback = new OnResponseCallback() {
public void onResponseReceived(HttpResponse response) {
resp[0] = response;
}
};
// launch operation, result will be returned to myCallback.onResponseReceived()
// wait on a CountDownLatch as soon as operation is finished
return resp[0];
}
or
public HttpResponse myFunction(...) {
final AtomicReference<HttpResponse> resp = new AtomicReference<HttpResponse>();
OnResponseCallback myCallback = new OnResponseCallback() {
public void onResponseReceived(HttpResponse response) {
resp.set(response);
}
};
// launch operation, result will be returned to myCallback.onResponseReceived()
// wait on a CountDownLatch as soon as operation is finished
return resp.get();
}