Android Volley Request - within request populating custom class not working - java

I've got an Activity consisting of a buch of TextViews (fourteen) and two Buttons.
I have created a custom class named Lesson, wich basically has a constructor and getter methods for its variables.
Now, inside my onCreate() in my Activity I am calling two functions: 1.) populateLessonDetails(myURL) and 2.) populateLessonTextViews().
I have created a private Lesson mLesson; variable inside my Activity, above all the #Overrides, because I'm trying to use this variable to populate it later on.
So, populateLessonDetails(myURL) is basically making a JsonArrayRequest, getting all the data from the JSON inside the onResponse(), saving it to String variables still inside the onResponse() and then, still inside the onResponse() I am trying to populate the mLesson variable, by calling
mLesson = new Lesson(mName, mRoom, mExtra, mAddress, mPC, mCity, mStart, mDate, mRID, mMaxAtt, mCurrentAtt); - the variables used within the constructor are the String variables containing the JSON data.
I Log.i() the JSON data as well as the mLesson variables via its getter methods, and the data is there. Everything is fine.
Now, my populateLessonDetails() ends.
It returns to the onCreate() and continues with the next line of code, wich would be calling populateLessonTextViews().
This is where things went south...
As soon as the function is called I try to get the information stored inside mLesson via its getter methods to set it to the TextViews like so:
//Lesson Name Big
TextView lessonNameTextBig = (TextView) findViewById(R.id.text_activelesson_name_big);
lessonNameTextBig.setText(mLesson.getLessonName());
This is the proper way to do it, I've done it a bunch of times already, but my App crashes at the second line.
I have debugged it and I have noticed that mLesson is empty. My guess would be that me populating it inside the onResponse() of the JsonArrayRequest, which is inside the populateLessonDetails() is only valid for this particular function, the scope of the variable mLesson ends when the function returns to the onCreate() and the mLesson variable is empty again since it died with the function.
Now how can I fix this? Do I have to set mLesson as a parameter for the populateLessonDetails() and then also return it (currently the populate functions are void) ? Then save the return value into another variable of type Lesson and set this new variable as a parameter for the populateLessonTextViews() ?? I've tried a couple of those things but they didn't work, but maybe its just me not doing it right.
This is what my code looks like (the important part):
public class ActiveLesson extends AppCompatActivity {
// there are also some other variables up here
private Lesson mLesson;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_active_lesson);
requestQ = Volley.newRequestQueue(this);
Intent intent = getIntent();
Bundle extras = intent.getExtras();
mDatum = extras.getString("datum");
mRID = extras.getString("rid");
mVon = extras.getString("von");
myActiveLessonURLFiltered += "datum="+mDatum+"&rid="+mRID+"&von="+mVon;
populateLessonDetails(myActiveLessonURLFiltered);
populateLessonTextViews();
}
private void populateLessonDetails(String myActiveLessonURLFiltered) {
JsonArrayRequest lessonJAR = new JsonArrayRequest(myActiveLessonURLFiltered,
new Response.Listener<JSONArray>() {
#Override
public void onResponse(JSONArray response){
try{
for (int i=0; i < response.length(); i++)
{
JSONObject jsonObject = response.getJSONObject(i);
String mName = jsonObject.getString("Name");
String mRoom = jsonObject.getString("Raum");
String mExtra = jsonObject.getString("Zusatz");
String mAdresse = jsonObject.getString("Address");
String mPC = jsonObject.getString("PLZ");
String mCity = jsonObject.getString("City");
String mMaxAtt = jsonObject.getString("maxAnz");
String mCurrentAtt = jsonObject.getString("belegtAnz");
if(mExtra.length()==0 || mExtra == "null")
mExtra="";
if(mRoom.length()==0 || mRoom == "null")
mRoom="";
else
mRoom="Room: "+mRoom;
if(mName.length()==0 || mName == "null")
mName="";
mLesson = new Lesson(mName, mRoom, mExtra, mAdresse,
mPC, mCity, mVon, mDatum, mRID, mMaxAtt, mCurrentAtt);
Log.i("mmLesson"," Lesson with new = "+ mLesson.getLessonName()
+" "+mLesson.getLessonCity());
}
}catch (JSONException e){
e.printStackTrace();
}
}
},
new Response.ErrorListener(){
#Override
public void onErrorResponse(VolleyError error){
error.printStackTrace();
Toast.makeText(ActiveLesson.this, "No Lessons Available",
Toast.LENGTH_LONG).show();
}
}){
#Override
public Map<String, String> getHeaders() throws AuthFailureError {
HashMap<String, String> headers = new HashMap<String, String>();
headers.put("Accept", "application/json");
return headers;
}
};
requestQ.add(lessonJAR);
}
private void populateLessonTextViews(Lesson mLesson) {
//Lesson Name Big
TextView lessonNameTextBig = (TextView) findViewById(R.id.text_activelesson_name_big);
lessonNameTextBig.setText(mLesson.getLessonName());
// there are others lines of code like these two,
// but I've left them out, since they are all the same
}
If some could help me out I would appreciate it. Thank you!

The onResponse() method is a callback that is called later when the network request returned a value. The server does not respond with any delay. This means the populateLessonDetails(..) method get called from onCreate triggers an network request and return immedietly to the onCreate() call of this function and steps forward.
You have to take this in consideration. The best way to do this, call inside the onResponse the populateLessonTextViews() method. Then you can be sure that the content has been loaded.

Related

The right way to deal with a few network requests

I'm preety new in java/android.
I'm writing android app, which takes data from online api.
The problem is, I'm not sure if my concept is correct.
So my app send first request, but I need some of respond data to start next request.
Here is some example more or less how it looks right now:
public class MainActivity extends AppCompatActivity{
private int key = 0;
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new DownloadTask().execute("url of 1st request");
}
private class DownloadTask extends AsyncTask<String, Void, String> {
#Override
protected String doInBackground(String... params) {
try {
return downloadContent(params[0]);
} catch (IOException e) {
return "Unable to retrieve data. URL may be invalid.";
}
}
#Override
protected void onPostExecute(String result) {
if(key == 0){
// I know it's 1st request because my key == 0
JSONObject json = new JSONObject(result);
String id = json.getString("id");
key++;
new DownloadTask().execute( "url of 2nd request/" + id );
}else{
// I know it's 2st request because my key != 0
//here I'm getting data i need
// and I'm going to rest of my app
end(result);
}
}
}
private void end(String result){
//rest of my app
}
}
Code is working fine, but I wanted to know if it's proper way to do it.
Maybe you know another way to do that, I'm not asking for completly new code, but maybe some topic I should find and read.
If you want to do it asynchronously, i will recommend you using CompletableFuture's. It is very simple and look like that:
CompletableFuture
.supplyAsync(() -> yourFirstrequest())
.thenApplyAsync(yourInstance::getResponse)
.thenAcceptAsync(nextRequestClass::sendNextrequest);
or you can separate them by Future's like:
CompletableFuture firstRequest = CompletableFuture.supplyAsync(() -> yourFirstrequest());
CompletableFuture getResponse = firstRequest.thenApply(response -> someActionWithResponse(response));
it is very powerful and convenient framework.
Take a look at the java docs or android specific docs

How to get POJO from a volley callback, and use/return it in the calling function?

I'm implementing a simple mobile app with user accounts. Additionally, it must be structured in a layered architecture that cleanly separates presentation, logic and access to the database.
I'm currently able to send and get data from a server, using the volley library. However, this data is only available inside the onResponse method of the Response.Listener<String> passed as a parameter in the constructor of stringRequest object, later used to perform the request. I want to use the data that I get in the response to construct a User object that I could use all over my app, and keep the layered architecture as much as possible.
This is an example of the kind of method I've been aiming for:
public ResponseType insertUser (final Context context, final String id, final String name, final String password) {
//using a wrapper object because have to declare object as final to use
//inside inner class, so use field to assign value
final ResponseWrapper wrapper = new ResponseWrapper();
StringRequest stringRequest = new StringRequest(Request.Method.POST, BuildConfig.ip,
new Response.Listener<String>() {
#Override
public void onResponse(String response) {
wrapper.response = response.equals("") ?
ResponseWrapper.SUCCESS :
ResponseWrapper.DB_ERROR;
}
},
new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
wrapper.response = ResponseWrapper.CONNECTION_ERROR;
}
}){
#Override
protected Map<String, String> getParams() throws AuthFailureError {
Map<String,String> params = new HashMap<String,String>();
params.put("id",id);
params.put("name",name);
params.put("password",password);
return params;
}
};
RequestQueue requestQueue = Volley.newRequestQueue(context);
requestQueue.add(stringRequest);
//waiting for callback to modify field
while(wrapper.response == null);
return wrapper.response;
}
I've tried setting a field of an external object inside onResponse, waiting for the field to be changed to continue execution, to no avail. The code compiles and performs the request, but the field is kept unchanged. My research has suggested to me that this is something to be expected when dealing with asynchronous code.
Most examples I've read limit their scope to using Toast to show the response in the screen. A couple change activities inside the method, but this goes against the layer separation that I'm trying to achieve by performing presentation actions inside the database access layer (and potentially performing business logic too, as my app becomes more complex).
So, how can I get an object from inside the callback? (For example the String containing the response, or an enum indicating the result of an operation).
If this isn't possible or advisable, how could I structure the code to keep the separation of concerns?
My thanks in advance for any suggestion that could steer me in the right direction.

How to save the value of a variable inside a Retrofit call?

I am trying to retrieve the value of a certain field from the header using a Retrofit call to be used to be sent back to server. I am successful in getting the value inside the try block and send it back immediately in the try block too. But when I try the same outside the call instance, the value of abc (which is where I assigned the value of the response header) is lost. I have already declared the String abc as a global variable. How do I save the value of the string?
public class MainActivity extends AppCompatActivity {
private static final String LOG_TAG = "MainActivityClass";
String abc;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ApiInterface apiService = ApiClient.getClient().create(ApiInterface.class);
Call<List<TrendingModel>> call = apiService.getAllTodos();
call.enqueue(new Callback<List<TrendingModel>>() {
#Override
public void onResponse(Call<List<TrendingModel>> call, Response<List<TrendingModel>> response) {
try {
List<TrendingModel> todoModels = response.body(); // WHERE WE GET THE RESPONSE BODY
abc = response.headers().get("Tanand"); // WHERE WE GET THE RESPONSE HEADER AND ASSIGN IT TO abc, WHICH WE DECLARED GLOBALLY
ApiClient.getClient(abc).create(ApiInterface.class); // PASSING THE abc VARIABLE TO THE GETCLIENT(TOKEN) METHOD WITHIN
// THE SAME TRY BLOCK WHICH WORKDS
} catch (Exception e) {
Log.d("onResponse", "There is an error");
e.printStackTrace();
}
}
#Override
public void onFailure(Call<List<TrendingModel>> call, Throwable t) {
Log.d("onFailure", t.toString());
}
});
ApiClient.getClient(abc).create(ApiInterface.class); // VALUE OF abc IS NOT PERSISTED HERE (abc IS NULL) ALTHOUGH WE DECLARED IT GLOBALLY
}
}
Try to call method inside OnResponse method .
because onResponse() method runs in background until the data is fetched.
If you want to access the data of response then call your method inside it .
And outside all statements are called before the response data finished which is why it doesn't give you actual data .
As a Good Practice, use the below method.
Just create a method inside the class and call your all statements inside it.
Now call your method inside onResponse method .
You can use a setter method within the onResponse method of Retrofit.
This answer explains how you can go about it https://stackoverflow.com/a/63060520/10123715

java can't find local variable in reterofit 2 api call

I have done an API call to retrieve a list of messages, then i want to check if each message has a flag of 2 and then if it does do another API call to receive information on whether the message has been "paid" and if it has alter the object message to paid = true;
Here is my failed attempt.
for (int i = 0; i < chatHistory.getData().size(); i++) {
final ChatMessage chatMessage = chatHistory.getData().get(i).getBody();
if (chatMessage.flag.equals("2")) {
RestClient.getInstance().getApiService().getPaymentRequest(chatMessage.payment_request_id, new Callback<SinglePaymentRequest>() {
#Override
public void success(SinglePaymentRequest singlePaymentRequest, Response response) {
Payment payment = singlePaymentRequest.getPayment();
if(payment.getStatus().equals("paid")) {
chatMessage.isPaid=true;
}
}
#Override
public void failure(RetrofitError error) {
System.out.println("fail");
}
});
}
chatMessages.add(chatMessage);
Log.e("chat history", chatMessage.from);
}
addData(chatMessages);
The problem I am facing is that the api call cannot find local variable chatmessage, any ideas as to why this is?
Thanks
Notice the bit of code new Callback<SinglePaymentRequest>() that creates your new Callback object? It does not have access to the variables outside it, for good reason too.
What you should be doing, is calling a setter method that's part of your container class (the one that is the parent of the Callback) that will, in turn manipulate the values that you want to change.

Get a variable from an anonymous class

I have the following situation :
final String value = null;
AsyncHttpClient client = new AsyncHttpClient();
client.get("http://www.google.com", new AsyncHttpResponseHandler() {
#Override
public void onSuccess(String response) {
value = response;
}
});
System.out.println(value);
I am running this code from my main class.
Now I need to be able to use the variable (String Response) from the over ridden class in my main class.
How am i meant to go about getting this variable, as clearly what i have doesnt work.
ps, suggest an edit for the name, I didnt know what to call it.
Your problem doesn't have to do with classes; it's that you're using asynchronous code synchronously. Anything you want to do with the string response must be within the onSuccess handler (or a function called by it).

Categories