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
Related
I am attempting to do a login for an android app. My problem is that when I call login from Activity.java, the Service.java method uses call.enqueue from Retrofit and it is async. So I am not able to wait and consume the response in the Activity.java method (so that I can handle what happens next).
Therefore, I decided that if I can setup a listener of some sort to capture when the response object changes, that I can handle what happens next in the app.
I have been looking into RxJava but no examples that I have seen work across multiple classes or seem to actually work properly. It would be extremely helpful to have some basic syntax that I could apply to this that would allow me to consume the login response object so that I can handle what happens next in the application. I am not married to RxJava either so if there is a different way to approach this that will let me consume this object, I'm fine with that. Thanks!!
GlobalData.java
public class GlobalData {
public static LoginResponse globalResponse;
}
Activity.java
public class Activity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
// Can I subscribe here and do some logic for when globalResponse changes?!?
}
public void login() {
// create login request object
// ...
// call service method
LoginResponse response = service.loginMethod(request);
// Can I await the async in loginMethod so that I run logic after the call?
}
}
Service.java
public class Service implements ServiceInterface {
#Override
public loginMethod(LoginRequest request) {
// Setup Retrofit for api call
// ...
Call<LoginResponse> call = apiService.login(request);
// async call to get a response from the api for login
call.enqueue(new Callback<LoginResponse>() {
#Override
public void onResponse(Call<LoginResponse> call, Response<LoginResponse> response) {
GlobalData.globalResponse = response.body(); // updates global data variable with the response from login
}
}
}
}
Check the next blogs to know how use Rx and retrofit together
Rxjava & Retrofit: http://www.baeldung.com/retrofit-rxjava
How subscribe an observable object: http://www.vogella.com/tutorials/RxJava/article.html#creating-observables-subscribing-to-them-and-disposing-them
I think with that you can continue with you login flow
public class Activity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
// Can I subscribe here and do some logic for when globalResponse changes?!?
Yes, but only if you use a BehaviorSubject/BehaviorRelay (I vote Relay, see BehaviorRelay).
So you have
public class GlobalData {
private GlobalData() {}
public static LoginResponse globalResponse;
}
But it should be something like
public class GlobalData {
private GlobalData() {}
public static BehaviorRelay<Optional<LoginResponse>> globalResponse = BehaviorRelay.createDefault(Optional.absent());
}
And
public void onResponse(Call<LoginResponse> call, Response<LoginResponse> response) {
LoginResponse login = response.body();
if(login != null) {
GlobalData.globalResponse.accept(Optional.of(response.body());
}
}
Bonus points if you hide the Relay and expose a method that writes into it, and another that shows it as Observable<Optional<T>>.
Anyways now you can do
Disposable disposable = GlobalData.globalResponse.subscribe((opt) -> {
if(opt.isPresent()) {
LoginResponse data = opt.get();
// do whatever
}
});
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.
(Scroll at the end of question to see the final solution)
Playing around with the Retrofit Android library. I am trying to push a POST request into a web server that is supposed to return 3 fields after a successful call to a "/login" method through POST. Information:
End point: http://example.us.es:3456
Server-side method to perform the login: /login
Required parameters to the server-side method: "user" and "password"
HTTP method allowed by server administrator: POST
No matter what values for "user" and "password" the app client enters, the web server should send a single JSONObject containing three fields:
ok: string with values "true" or "false" (false would mean that the credentials were not valid).
msg: would carry a string message in case there was an error (for example, invalid credentials or database error)
data: another JSONObject that in this method contains one single name/value pair, the id_session (string containing the session identifier). Other methods contain several name/value pairs; this is not the case for the login method.
Basing myself in this information, the first thing I did is create a POJO Java method that looks like this:
POJO Method (LoginInfo_POJO.java)
package com.example.joselopez.prueba1;
import java.util.List;
public class LoginInfo_POJO {
public String _ok;
public String _msg;
public Dataset _dataset;
class Dataset {
String _idsession;
}
}
The next thing I did is create an interface containing the login method (I'd add other methods here after I can successfully log in):
API METHODS IN INTERFACE (IApiMethods.java)
package com.example.joselopez.prueba1;
import retrofit.http.POST;
import retrofit.http.Query;
public interface IApiMethods {
// Log-in method
#FormUrlEncoded
#POST("/login")
LoginInfo_POJO logIn(#Query("user") String user,
#Query("password") String password);
}
Almost there. Now I create a class that extends from AsyncTask that will perform the networking operations in a separate thread. This class is inside the "MainActivity.java" file.
Main Activity (MainActivity.java)
...
...
private class BackgroundTask_LogIn extends AsyncTask<Void, Void, LoginInfo_POJO> {
RestAdapter restAdapter;
#Override
protected LoginInfo_POJO doInBackground(Void... params) {
IApiMethods methods = restAdapter.create(IApiMethods.class);
LoginInfo_POJO loginInfo = methods.logIn(mUser, mPassword);
return loginInfo;
}
#Override
protected void onPreExecute() {
restAdapter = new RestAdapter.Builder()
.setEndpoint("http://example.us.es:3456")
.build();
}
#Override
protected void onPostExecute(LoginInfo_POJO loginInfo_pojo) {
tv.setText("onPostExecute()");
tv.setText(loginInfo_pojo._ok + "\n\n");
tv.setText(tv.getText() + loginInfo_pojo._msg + "\n\n");
tv.setText(tv.getText() + loginInfo_pojo.data.id_sesion);
}
}
}
The MainActivity layout contains a single TextView (inside a RelativeLayout) whose id is "textView", and is instantiated in code as "tv" as you will see next. The complete code for MainActivity is:
Main Activity (MainActivity.java)
package com.example.joselopez.prueba1;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.TextView;
import retrofit.RestAdapter;
public class MainActivity extends AppCompatActivity {
TextView tv;
String mUser, mPassword;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView) findViewById(R.id.textView);
mUser = "test_user";
mPassword = "test_password";
BackgroundTask_LogIn tryLogin = new BackgroundTask_LogIn();
tryLogin.execute();
}
private class BackgroundTask_LogIn extends AsyncTask<Void, Void, LoginInfo_POJO> { ... }
Everything should be working. But it is not, and after a bit of debugging I found that the onPostExecute() method inside class BackgroundTask_LogIn stops in line:
for (LoginInfo_POJO.Dataset dataset : loginInfo_pojo._dataset) {
The error thrown is:
com.example.joselopez.prueba1 E/AndroidRuntime﹕ FATAL EXCEPTION: main
java.lang.NullPointerException
So I set a breakpoint at this line and guess what? My LoginInfo_POJO instance is holding these values for its internal variables:
_ok = null
_msg = null
_dataset = null
This means my variables aren't being populated from the server response, BUT the connection seems to be successful as the doInBackground method runs entirely and onPostExecute is being called.
So what do you think? Maybe I am not carrying out the POST request the right way?
UPDATE
As #Gaëtan said, I made a huge error in my POJO class; local variable names there MUST be EQUAL to those in the resulting JSON. I said that I was expecting fields "ok", "msg", "data" and "id_session" from the JSON, but the local variables inside my LoginInfo_POJO have names "_ok", "_msg", "_dataset", "_idsession" (notice the leading underscores). This is a huge error from a Retrofit perspective, so rewriting the POJO method accounting for this will eventually solve the problem.
A couple of information about how to use Retrofit:
The name of the fields in your POJO must match the first in the JSON response. Here, your fields are named _ok, _msg and _dataset while the JSON response contains ok, msg and data. You have two options here: either rename the fields to match the JSON response, or use the #SerializedName annotation on each field to give the name of the JSON field.
public class LoginInfo_POJO {
// If you rename the fields
public String ok;
public String msg;
public List<Dataset> data;
// If you use annotation
#SerializedName("ok")
public String _ok;
#SerializedName("msg")
public String _msg;
#SerializedName("data")
public String _dataset;
}
Retrofit provide a way to not use AsyncTask. Instead of using a return type for your API method (here LoginInfo_POJO logIn(String, String), use a last parameter of type Callback<LoginInfo_POJO>. The request will be executed on a background thread by retrofit, and the callback will be called on the main thread when the request is complete (or failed if something went wrong).
See the documentation for more information, in the SYNCHRONOUS VS. ASYNCHRONOUS VS. OBSERVABLE section.
You could try to set the logLevel option to RestAdapter.LogLevel.FULL when constructing your adapter inside your onPreExecute to get perhaps more information on what's actually going on.
From Square Retrofit API Declaration:
If you need to take a closer look at the requests and responses you can easily add logging levels to the RestAdapter with the LogLevel property.
Example with logLevel set to FULL:
RestAdapter restAdapter = new RestAdapter.Builder()
.setLogLevel(RestAdapter.LogLevel.FULL)
.setEndpoint("https://api.github.com")
.build();
That said, it's true that you don't need AsyncTask with Retrofit.
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).
So I have this GWT code that handles RPC requests maintain states(ready, waiting, error etc).
And I would like to check if the class change its states correctly after each call, set response variables etc.
Now how should I proceed to test that without making actual requests to the server(that could run into errors in the server it self).
I think I could mock the request callback class somehow but it is invisible to the test.
I'm lost, help!
Sample of the code below(I'll post the whole thing later in case anyone wants).
public class RPCHandler
{
public RPCHandler(String method,String[] argumentsName,
String[][] argumentsValues)
{
this.method = method;
this.argumentsName = argumentsName;
this.argumentsValues = argumentsValues;
}
/**
* Method that creates a RPC request using JSON in a POST
*
*/
public void rpcRequest(){
if(currentState == HandlerState.WAITING_RESPONSE)return;
currentState = HandlerState.WAITING_RESPONSE;
// Append watch list stock symbols to query URL.
url = URL.encode(url);
url += "action=";
url += method;
// Send request to server and catch any errors.
RequestBuilder builder = new RequestBuilder(RequestBuilder.POST, url);
String requestData = parseToJSON(argumentsName, argumentsValues);
try{
Request request = builder.sendRequest(requestData, new RequestCallback()
{
public void onError(Request request, Throwable exception)
{
setRPCException(new Exception("Error while saving. Action="+method));
setCurrentState(HandlerState.ON_ERROR);
}
//Few other error, response received hander methods after this point.
}
}
It looks like you're trying to mock out the actual transport so you should build a mock of the RequestBuilder class. In JMockit, you could write:
public class MockRequestBuilder
{
public void $init( int method, String url)
{
/* check values and/or store for later */
}
public Request sendRequest( String data, RequestCallback callback )
{
/* check values and/or store for later */
}
}
You'll need to fill in the details of the what you want the mock to do. Also, you can isolate the callback testing if you moved the callback to a named class instance inside of your outer class:
public class MyGWTClass
{
protected static class RpcCallback extends RequestCallback
{
public void onError(...) { ... }
}
}
By moving the callback object into a class and using a factory method, you can create tests that only check the callback.