Reputation: 242
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:
No matter what values for "user" and "password" the app client enters, the web server should send a single JSONObject containing three fields:
Basing myself in this information, the first thing I did is create a POJO Java method that looks like this:
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):
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.
...
...
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:
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:
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?
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.
Upvotes: 2
Views: 4550
Reputation: 141
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.
Upvotes: 1
Reputation: 12562
A couple of information about how to use Retrofit:
_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;
}
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.
Upvotes: 1