0

I'm using Retrofit 2 in Android Studio to get stop information in JSON form from the CUMTD api for stops and for some reason I get this error java.lang.NullPointerException: Attempt to invoke interface method 'java.lang.Object java.util.List.get(int)' on a null object reference, despite my GET request and query parameters being okay.

My MTD api interface:

import java.util.List;
import retrofit2.Call;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.http.GET;
import retrofit2.http.Path;
import retrofit2.http.Query;

/**
 * Class that details the request(s) that we will call
 */

public interface MTDApi{
    @GET("GetStops")
    Call<List<UserModel>> loadStops(
            @Query("api_key") String key,
            @Query("stop name") String stop
    );

    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl("https://developer.cumtd.com/api/v2.2/json/")
            .addConverterFactory(GsonConverterFactory.create())
            .build();

}

My UserModel class:

public class UserModel {
    String stop_id;
    public String getStop_id(){
        return stop_id;
    }
    public void setStop_id(String stop_id){
        this.stop_id = stop_id;
    }
}

My main activity:

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import java.util.List;
import java.util.Random;

import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

import static android.R.attr.x;
import static android.media.CamcorderProfile.get;
import static com.example.neelpatel.weatherapp.MTDApi.retrofit;

public class MainActivity extends AppCompatActivity {

    String key= "<APIKEY>";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);//Starts Retrofit
        final MTDApi mtdApi = MTDApi.retrofit.create(MTDApi.class);

        //Sets up Button and EditText for use in this class
        final EditText edit = (EditText) findViewById(R.id.edit);
        Button requestButton = (Button) findViewById(R.id.button);

        //Behavior once button is clicked
        requestButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                String s = edit.getText().toString();
                //Sets up up the API call
                Call<List<UserModel>> call = mtdApi.loadStops(key,s);

                //Runs the call on a different thread
                call.enqueue(new Callback<List<UserModel>>() {
                    @Override
                    //Once the call has finished

                public void onResponse(Call<List<UserModel>> call, Response<List<UserModel>> response) {
                    //Gets the list of stops
                    List<UserModel> stops = response.body();
                    String text = stops.get(0).getStop_id();
                    edit.setText(text);
                }

                    @Override
                    //If the call failed
                    public void onFailure(Call<List<UserModel>> call, Throwable t) {
                        edit.setText("Request Failed");
                        Log.e("RequestCall", "Request failed");
                    }
                });
            }
        });
    }
}

My activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.neelpatel.weatherapp.MainActivity">

    <EditText
        android:id="@+id/edit"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_above="@+id/button"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_marginBottom="133dp" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Request"
        tools:layout_editor_absoluteX="16dp"
        tools:layout_editor_absoluteY="16dp"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="95dp" />

</RelativeLayout>
Community
  • 1
  • 1

3 Answers3

0

In onResponse you should check that response.isSuccessful() and check that stops != null. It seems that your server returns error.

Vladimir Berezkin
  • 3,580
  • 4
  • 27
  • 33
0

First of all, you should always check whether the response was successful (i.e. it's in the range of [200, 300) of HTTP status codes). You can do this in your onResponse(...) method:

 public void onResponse(Call<List<UserModel>> call, Response<List<UserModel>> response) {
      if (response.isSuccessful()) {
           //Gets the list of stops
           List<UserModel> stops = response.body();
           String text = stops.get(0).getStop_id();
           edit.setText(text);
      } else {
           // show error message
      }
 }

Also, your query is wrong. According to the documentation of GetStops, it expects a key query parameter, not an api_key one (and also you're passing a stop name one which has a space in it). So your URL (in the end) will (should) look like https://developer.cumtd.com/api/v2.2/json/GetStops?key=<your_api_key>. This means that your endpoint description in the interface should look like this:

@GET("GetStops")
Call<List<UserModel>> loadStops(@Query("key") String key);

It's also worth mentioning that your expected response is not matching the actual response's structure which you can see in the sample. If you don't want to create that structure manually, you can use JSON to POJO converters, such as this one that you can use to, in fact, convert the sample into a proper POJO model structure.

Gergely Kőrössy
  • 5,620
  • 3
  • 28
  • 44
  • But it says api key for the parameter so I can't just pass in the api key as a string for it? – Neel Patel Jul 01 '17 at 16:58
  • I clarified this part in my answer. Also note that your expected model's structure is not matching the actual response's structure. – Gergely Kőrössy Jul 01 '17 at 17:31
  • So i should add the key to the method in the main activity then when i call loadstops? – Neel Patel Jul 01 '17 at 18:46
  • You do this right now by calling `mtdApi.loadStops(key, s);`. Only thing is that you don't need the `s` parameter anymore. But as I see you want to do some kind of a search. Shouldn't you use [GetStopsBySearch](https://developer.cumtd.com/documentation/v2.2/method/GetStopsBySearch) instead? – Gergely Kőrössy Jul 01 '17 at 20:47
  • Yeah I changed it to getstopsbysearch https://stackoverflow.com/questions/44864916/issues-with-get-request-using-retrofit-in-android-studio this is the link to the new question for it, apparently I cannot connect now for some reason, would you mind taking a look at the code in this question? – Neel Patel Jul 01 '17 at 22:20
0

According to the documentation, the server return a JSON Object and not a JSON Array, as you seem to expect when you wrote the type List<UserModel> in your retrofit interface. The array you want is the stops field in the object, so you may want to either:

  • create a POJO that matches the returned JSON Object and get the array from there
  • create a custom Gson deserializer as explained in answer.
Matthieu Harlé
  • 739
  • 1
  • 13
  • 32