Daniel Spiess
Daniel Spiess

Reputation: 222

Retrofit / Gson puts empty model in List<>

Im trying to retrive json with retrofit and gson from the API http://api.themoviedb.org/3/movie/. Im my main activity it is working fine, but when i call some additional information in my DetailsActivity i figure out that the models in my array are emty. This is weird because i am recieving even the right number of objects in the array from the api. But when i Log a value from the model (for example Log.d(TAG, movieTrailers.get(0).getName()) the app crashes. Whit an if statement i figure out that this value is null.

This is the API http://api.themoviedb.org/3/movie/297802/videos?api_key= for example. (Unfortunatly i am not allowed to post the api key) It contains in a jsonarray of videos about the movie.

This is my Code:

The Model:

public class MovieTrailer {

    @SerializedName("key")
    private String key;
    @SerializedName("name")
    private String name;
    @SerializedName("type")
    private String type;

    public MovieTrailer(String key, String name, String type){
        this.key = key;
        this.name = name;
        this.type = type;
    }

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

}

The Model for the List:

public class TrailersList {
    @SerializedName("results")
    @Expose
    private List<MovieTrailer> trailers = null;

    public List<MovieTrailer> getTrailers() {
        return trailers;
    }

    public void setTrailers(List<MovieTrailer> trailers) {
        this.trailers = trailers;
    }
}

The Adapter:

public class VideoAdapter extends RecyclerView.Adapter<VideoAdapter.VideoAdapterViewHolder> {

    private List<MovieTrailer> movieTrailers;

    private final  VideoOnClickHandler videoOnClickHandler;

    public interface VideoOnClickHandler{
        void onClick(MovieTrailer movieTrailer);
    }

    public VideoAdapter(VideoOnClickHandler onClickHandler) {videoOnClickHandler = onClickHandler;}

    public class VideoAdapterViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{

        public final TextView videoTypeText;
        public final TextView videoNameText;

        public VideoAdapterViewHolder(View view){
            super(view);
            videoTypeText = (TextView) view.findViewById(R.id.tv_video_type);
            videoNameText = (TextView) view.findViewById(R.id.tv_video_name);
            view.setOnClickListener(this);
        }

        @Override
        public void onClick(View view) {
            int adapterPosition = getAdapterPosition();
            MovieTrailer movieTrailer = movieTrailers.get(adapterPosition);
            videoOnClickHandler.onClick(movieTrailer);
        }
    }

    @NonNull
    @Override
    public VideoAdapterViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
        Context context = viewGroup.getContext();
        int gridItem = R.layout.movie_trailer;
        LayoutInflater inflater = LayoutInflater.from(context);
        boolean shouldAttachToParentImmediately = false;

        View view = inflater.inflate(gridItem, viewGroup,shouldAttachToParentImmediately);

        return new VideoAdapter.VideoAdapterViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull VideoAdapterViewHolder videoAdapterViewHolder, int i) {
        MovieTrailer movieTrailer = movieTrailers.get(i);
        videoAdapterViewHolder.videoTypeText.setText(movieTrailer.getType());
        videoAdapterViewHolder.videoNameText.setText(movieTrailer.getName());
    }

    @Override
    public int getItemCount() {
        if (null == movieTrailers) return 0;
        return movieTrailers.size();
    }

    //Function to set movieTrailers
    public void setMovieTrailerArray(List<MovieTrailer> trailerArrayToSet){
        movieTrailers = trailerArrayToSet;
        notifyDataSetChanged();
    }


}

The Interface:

public interface GetDataService {

    @GET("{path}?api_key=" + BuildConfig.API_KEY)
    Call<MoviesList> getAllMovies(@Path("path") String path);

    @GET("{movieId}/reviews?api_key=" + BuildConfig.API_KEY)
    Call<TrailersList> getAllTrailers(@Path("movieId") String movieId);

    @GET("{movieId}/videos?api_key=" + BuildConfig.API_KEY)
    Call<ReviewsList> getAllReviews(@Path("movieId") String movieId);
}

The Retrofit Instance:

public class RetrofitClientInstance {

    //Base URL for API request
    private static final String MOVIE_DATABASE_URL_POPULAR =
            "http://api.themoviedb.org/3/movie/";

    /**
     * Get Retrofit Instance
     */
    private static Retrofit getRetrofitInstance() {
        return new Retrofit.Builder()
                .baseUrl(MOVIE_DATABASE_URL_POPULAR)
                .addConverterFactory(GsonConverterFactory.create())
                .build();
    }

    /**
     * Get API Service
     *
     * @return API Service
     */
    public static GetDataService getApiService() {
        return getRetrofitInstance().create(GetDataService.class);
    }
}

And the method from the DetailsActivity:

private void loadMovieTrailers(String movieId){
        GetDataService api = RetrofitClientInstance.getApiService();
        Call<TrailersList> call = api.getAllTrailers(movieId);

        call.enqueue(new Callback<TrailersList>() {
            @Override
            public void onResponse(Call<TrailersList> call, Response<TrailersList> response) {
                if(response.isSuccessful()){
                    movieTrailers = response.body().getTrailers();
                    if(movieTrailers.get(0).getName() == null){
                        Log.d("MODEL", "Null");
                    }else {
                        Log.d("MODEL", "Not Null");
                    }

                    videoAdapter.setMovieTrailerArray(movieTrailers);
                }
            }

            @Override
            public void onFailure(Call<TrailersList> call, Throwable t) {
            }
        });
    }

Someone has an idea where the mistake is?

Upvotes: 4

Views: 1244

Answers (3)

touhid udoy
touhid udoy

Reputation: 4442

In my case, my debug apk was working fine, but when release apk was created, all data of model was showing empty except the data was annotated with Expose and SerializedName. Although my key and variable name was same, but I still had to add SerializedName, I am not sure why.

So, if you haven't added those, please add these two annotation.

In short convert private int id; to this @Expose @SerializedName("id") private int id;

Upvotes: 0

Ridcully
Ridcully

Reputation: 23665

In your MovieTrailer class, the @Expose annotations are missing for the fields. Thus, Gson creates the objects, but doesn't find any fields to put the actual data in.

Btw. The @SerializedFieldName is only needed if the field name in the JSON is different than the field name in your model.

Upvotes: 1

Shashanth
Shashanth

Reputation: 5190

I think the problem is in your interface class with these methods,

@GET("{movieId}/reviews?api_key=" + BuildConfig.API_KEY)
Call<TrailersList> getAllTrailers(@Path("movieId") String movieId);

@GET("{movieId}/videos?api_key=" + BuildConfig.API_KEY)
Call<ReviewsList> getAllReviews(@Path("movieId") String movieId);

From {movieId}/reviews you'll get the following JSON response,

{
  "id": 297761,
  "page": 1,
  "results": [
    {
      "id": "57a814dc9251415cfb00309a",
      "author": "Frank Ochieng",
      "content": "Summertime 2016 has not been very kind to DC Comics-based personalities looking to shine consistently like their big screen Marvel Comics counterparts.",
      "url": "https://www.themoviedb.org/review/57a814dc9251415cfb00309a"
    }
  ],
  "total_pages": 1,
  "total_results": 1
}

and {movieId}/videos returns,

{
  "id": 550,
  "results": [
    {
      "id": "533ec654c3a36854480003eb",
      "iso_639_1": "en",
      "iso_3166_1": "US",
      "key": "SUXWAEX2jlg",
      "name": "Trailer 1",
      "site": "YouTube",
      "size": 720,
      "type": "Trailer"
    }
  ]
}

So, both of them returns almost same result with JSONArray (results) enclosed withing JSONObject. JSON response is proper there's no problem at all.

While parsing JSON response, Gson converter factory fails to find the equivalent POJO class for the response. You don't even get any errors because both are returning same type response.

A simple fix is to change (inter change) the URLs of the following methods.

@GET("{movieId}/videos?api_key=" + BuildConfig.API_KEY)   // change from reviews to videos
Call<TrailersList> getAllTrailers(@Path("movieId") String movieId);  

@GET("{movieId}/reviews?api_key=" + BuildConfig.API_KEY)   // change from videos to reviews
Call<ReviewsList> getAllReviews(@Path("movieId") String movieId);

That's it!

Upvotes: 0

Related Questions