简体   繁体   English

使用 Retrofit 库的 NullPointerException - Android

[英]NullPointerException using Retrofit library - Android

(Scroll at the end of question to see the final solution) (在问题末尾滚动以查看最终解决方案)

Playing around with the Retrofit Android library.玩弄 Retrofit Android 库。 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.我正在尝试将 POST 请求推送到 Web 服务器,该服务器应该在通过 POST 成功调用“/login”方法后返回 3 个字段。 Information:信息:

  1. End point: http://example.us.es:3456终点: http : //example.us.es : 3456
  2. Server-side method to perform the login: /login执行登录的服务器端方法: /login
  3. Required parameters to the server-side method: "user" and "password"服务器端方法的必需参数:“用户”和“密码”
  4. HTTP method allowed by server administrator: POST服务器管理员允许的 HTTP 方法:POST

No matter what values for "user" and "password" the app client enters, the web server should send a single JSONObject containing three fields:无论应用程序客户端输入的“用户”和“密码”是什么值,Web 服务器都应该发送一个包含三个字段的 JSONObject:

  1. ok : string with values "true" or "false" (false would mean that the credentials were not valid). ok :值为“true”或“false”的字符串(false 表示凭据无效)。
  2. msg : would carry a string message in case there was an error (for example, invalid credentials or database error) msg :如果出现错误(例如,无效的凭据或数据库错误),将携带字符串消息
  3. data : another JSONObject that in this method contains one single name/value pair, the id_session (string containing the session identifier). data :在此方法中包含一个名称/值对的另一个 JSONObject,即id_session (包含会话标识符的字符串)。 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 Java 方法,如下所示:

POJO Method ( LoginInfo_POJO.java ) POJO 方法( 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 )接口中的 API 方法 ( 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.现在我创建一个从 AsyncTask 扩展的类,它将在一个单独的线程中执行网络操作。 This class is inside the "MainActivity.java" file.此类位于“MainActivity.java”文件中。

Main Activity ( MainActivity.java )主要活动( 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. MainActivity 布局包含一个 TextView(在 RelativeLayout 内),其 ID 为“textView”,并在代码中实例化为“tv”,如下所示。 The complete code for MainActivity is: MainActivity的完整代码是:

Main Activity ( MainActivity.java )主要活动( 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:但事实并非如此,经过一些调试后,我发现类 BackgroundTask_LogIn 中的 onPostExecute() 方法在队列中停止:

for (LoginInfo_POJO.Dataset dataset : loginInfo_pojo._dataset) {

The error thrown is:抛出的错误是:

com.example.joselopez.prueba1 E/AndroidRuntime﹕ FATAL EXCEPTION: main java.lang.NullPointerException com.example.joselopez.prueba1 E/AndroidRuntime:致命异常:主要 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:我的 LoginInfo_POJO 实例为其内部变量保存了这些值:

  • _ok = null _ok = 空
  • _msg = null _msg = 空
  • _dataset = null _dataset = 空

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.这意味着我的变量没有从服务器响应中填充,但是连接似乎成功了,因为 doInBackground 方法完全运行并且 onPostExecute 正在被调用。

So what do you think?所以你怎么看? Maybe I am not carrying out the POST request the right way?也许我没有以正确的方式执行 POST 请求?


UPDATE更新

As @Gaëtan said, I made a huge error in my POJO class;正如@Gaëtan 所说,我在 POJO 类中犯了一个巨大的错误; local variable names there MUST be EQUAL to those in the resulting JSON.局部变量名称必须与结果 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).我说我期待来自 JSON 的字段“ok”、“msg”、“data”和“id_session”,但我的 LoginInfo_POJO 中的局部变量具有名称“_ok”、“_msg”、“_dataset”、“_idsession” (注意前导下划线)。 This is a huge error from a Retrofit perspective, so rewriting the POJO method accounting for this will eventually solve the problem.从 Retrofit 的角度来看,这是一个巨大的错误,因此为此重写 POJO 方法最终将解决问题。

A couple of information about how to use Retrofit:关于如何使用 Retrofit 的一些信息:

  1. The name of the fields in your POJO must match the first in the JSON response. POJO 中字段的名称必须与 JSON 响应中的第一个匹配。 Here, your fields are named _ok , _msg and _dataset while the JSON response contains ok , msg and data .在这里,您的字段被命名为_ok_msg_dataset而 JSON 响应包含okmsgdata 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.您在这里有两个选择:重命名字段以匹配 JSON 响应,或者在每个字段上使用@SerializedName注释来给出 JSON 字段的名称。
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;
}
  1. Retrofit provide a way to not use AsyncTask . Retrofit 提供了一种不使用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).不使用 API 方法的返回类型(此处为LoginInfo_POJO logIn(String, String ),使用类型为Callback<LoginInfo_POJO>的最后一个参数。请求将通过改造在后台线程上执行,并且回调将被调用请求完成时的主线程(如果出现问题则失败)。

See the documentation for more information, in the SYNCHRONOUS VS.有关更多信息,请参阅SYNCHRONOUS VS 中文档 ASYNCHRONOUS VS.异步 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.在 onPreExecute 中构建适配器时,您可以尝试将 logLevel 选项设置为 RestAdapter.LogLevel.FULL 以获取有关实际情况的更多信息。

From Square Retrofit API Declaration :来自Square Retrofit API 声明

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.如果您需要仔细查看请求和响应,您可以使用 LogLevel 属性轻松地向 RestAdapter 添加日志级别。

Example with logLevel set to FULL: logLevel 设置为 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.也就是说,确实不需要 AsyncTask with Retrofit。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM