简体   繁体   中英

Parsing nested JSON with Retrofit library into a RecyclerView: Having a load data error

I'm a newbie in the Android development an am trying to parse complex JSON structure with the help of rertofit library from the http://api.tvmaze.com/search/shows?q=girls . But my project fails in loading data. I suppose that the problem is in the GSON model, but I'm in complete bewilderment what's exactly wrong. I need to fetch only image and title I have already included the internet permission in the manifest response looks like that:

[
  {
    "score": 17.314238,
    "show": {
      "id": 139,
      "url": "http://www.tvmaze.com/shows/139/girls",
      "name": "Girls",
      "type": "Scripted",
      "language": "English",
      "genres": [
        "Drama",
        "Romance"
      ],
      "status": "Ended",
      "runtime": 30,
      "premiered": "2012-04-15",
      "officialSite": "http://www.hbo.com/girls",
      "schedule": {
        "time": "22:00",
        "days": [
          "Sunday"
        ]
      },
      "rating": {
        "average": 6.7
      },
      "weight": 94,
      "network": {
        "id": 8,
        "name": "HBO",
        "country": {
          "name": "United States",
          "code": "US",
          "timezone": "America/New_York"
        }
      },
      "webChannel": null,
      "externals": {
        "tvrage": 30124,
        "thetvdb": 220411,
        "imdb": "tt1723816"
      },
      "image": {
        "medium": "http://static.tvmaze.com/uploads/images/medium_portrait/31/78286.jpg",
        "original": "http://static.tvmaze.com/uploads/images/original_untouched/31/78286.jpg"
      },
      "summary": "<p>This Emmy winning series is a comic look at the assorted humiliations and rare triumphs of a group of girls in their 20s.</p>",
      "updated": 1538265422,
      "_links": {
        "self": {
          "href": "http://api.tvmaze.com/shows/139"
        },
        "previousepisode": {
          "href": "http://api.tvmaze.com/episodes/1079686"
        }
      }
    }
  },
  {
    "score": 13.229476,
    "show": {
      "id": 23542,
      "url": "http://www.tvmaze.com/shows/23542/good-girls",
      "name": "Good Girls",
      "type": "Scripted",
      "language": "English",
      "genres": [
        "Drama",
        "Comedy",
        "Crime"
      ],
      "status": "Running",
      "runtime": 60,
      "premiered": "2018-02-26",
      "officialSite": "https://www.nbc.com/good-girls?nbc=1",
      "schedule": {
        "time": "22:00",
        "days": [
          "Monday"
        ]
      },
      "rating": {
        "average": 7
      },
      "weight": 92,
      "network": {
        "id": 1,
        "name": "NBC",
        "country": {
          "name": "United States",
          "code": "US",
          "timezone": "America/New_York"
        }
      },
      "webChannel": null,
      "externals": {
        "tvrage": null,
        "thetvdb": 328577,
        "imdb": "tt6474378"
      },
      "image": {
        "medium": "http://static.tvmaze.com/uploads/images/medium_portrait/141/354343.jpg",
        "original": "http://static.tvmaze.com/uploads/images/original_untouched/141/354343.jpg"
      },
      "summary": "<p><b>Good Girls</b> follows three \"good girl\" suburban wives and mothers who suddenly find themselves in desperate circumstances and decide to stop playing it safe, and risk everything to take their power back.</p>",
      "updated": 1532345475,
      "_links": {
        "self": {
          "href": "http://api.tvmaze.com/shows/23542"
        },
        "previousepisode": {
          "href": "http://api.tvmaze.com/episodes/1425058"
        }
      }
    }
  },
......
]

I build my model class for fetching image and title, because i need only htese two items: public class Image:

import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;

public class Image {

    @SerializedName("medium")
    @Expose
    private String medium;
    @SerializedName("original")
    @Expose
    private String original;

    public String getMedium() {
        return medium;
    }

    public void setMedium(String medium) {
        this.medium = medium;
    }

    public String getOriginal() {
        return original;
    }

    public void setOriginal(String original) {
        this.original = original;
    }

}

public class Movie:

import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;

public class Movie {
    @SerializedName("name")
    @Expose
    private String name;
    @SerializedName("image")
    @Expose
    private Image image;
    public String getTitle() {
        return name;
    }

    public void setTitle(String name) {
        this.name = name;
    }
    public Image getImageUrl() {
        return image;
    }

    public void setImageUrl(Image image) {
        this.image = image;
    }
}

A recyclerview adapter for those items:

public class MovieAdapter extends RecyclerView.Adapter<MovieAdapter.MyViewHolder> implements Filterable {

private List<Movie> movieList;
private List<Movie> movieListFiltered;
private Context context;

public void setMovieList(Context context,final List<Movie> movieList){
    this.context = context;
    this.movieList = movieList;
    this.movieListFiltered = movieList;
}

@Override
public MovieAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

    View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item,parent,false);
    return new MyViewHolder(view);
}

@Override
public void onBindViewHolder(MovieAdapter.MyViewHolder holder, int position) {
    holder.title.setText(movieListFiltered.get(position).getTitle());
    GlideApp.with(context).load(movieList.get(position).getImageUrl()).listener(new RequestListener() {
        @Override
        public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) {
            // Log the GlideException here (locally or with a remote logging framework):
            Log.e(TAG, "Load failed", e);

            // You can also log the individual causes:
            for (Throwable t : e.getRootCauses()) {
                Log.e(TAG, "Caused by", t);

            }
            // Or, to log all root causes locally, you can use the built in helper method:
            e.logRootCauses(TAG);

            return false; // Allow calling onLoadFailed on the Target.
        }

        @Override
        public boolean onResourceReady(Object resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) {
            // Log successes here or use DataSource to keep track of cache hits and misses.

            return false; // Allow calling onResourceReady on the Target.

        }

    }).into(holder.image);
}

@Override
public int getItemCount() {

    if(movieList != null&& movieListFiltered!=null){
        return movieListFiltered.size();
    } else {
        return 0;
    }
}

@Override
public Filter getFilter() {
    return new Filter() {
        @Override
        protected FilterResults performFiltering(CharSequence charSequence) {
            String charString = charSequence.toString();
            if (charString.isEmpty()) {
                movieListFiltered = movieList;
            } else {
                List<Movie> filteredList = new ArrayList<>();
                for (Movie movie : movieList) {
                    if (movie.getTitle().toLowerCase().contains(charString.toLowerCase())) {
                        filteredList.add(movie);
                    }
                }
                movieListFiltered = filteredList;
            }

            FilterResults filterResults = new FilterResults();
            filterResults.values = movieListFiltered;
            return filterResults;
        }

        @Override
        protected void publishResults(CharSequence charSequence, FilterResults filterResults) {
            movieListFiltered = (ArrayList<Movie>) filterResults.values;
            notifyDataSetChanged();
        }
    };
}

public class MyViewHolder extends RecyclerView.ViewHolder {

    TextView title;
    ImageView image;

    public MyViewHolder(View view) {
        super(view);
        title = (TextView) view.findViewById(R.id.title);
        image = (ImageView)view.findViewById(R.id.image);
    }
}

}

My MainActivity:

public class MainActivity extends AppCompatActivity {
private SearchView searchView;
private RecyclerView recyclerView;
private MovieAdapter movieAdapter;
private List<Movie> movieList;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    movieList = new ArrayList<>();
    recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
    recyclerView.setLayoutManager(new LinearLayoutManager(this));
    movieAdapter = new MovieAdapter();
    recyclerView.setAdapter(movieAdapter);


    ApiInterface apiService = TvMazeApiClient.getClient().create(ApiInterface.class);
    Call<List<Movie>> call = apiService.getMovies();

    call.enqueue(new Callback<List<Movie>>() {
        @Override
        public void onResponse(Call<List<Movie>> call, Response<List<Movie>> response) {
            if(movieList != null) {
                movieList = response.body();
                Log.d("TAG", "Response = " + movieList);
                movieAdapter.setMovieList(getApplicationContext(), movieList);
            }
            else{
                Toast.makeText(MainActivity.this,"error", Toast.LENGTH_SHORT).show();
            }
        }

        @Override
        public void onFailure(Call<List<Movie>> call, Throwable t) {
            Log.d("TAG","Response = "+t.toString());
            Toast.makeText(MainActivity.this,t.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
        }
    });
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.options_menu, menu);

    SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
    searchView = (SearchView) menu.findItem(R.id.action_search)
            .getActionView();
    searchView.setSearchableInfo(searchManager
            .getSearchableInfo(getComponentName()));
    searchView.setMaxWidth(Integer.MAX_VALUE);

    searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
        @Override
        public boolean onQueryTextSubmit(String query) {
            movieAdapter.getFilter().filter(query);
            return false;
        }

        @Override
        public boolean onQueryTextChange(String query) {
            movieAdapter.getFilter().filter(query);
            return false;
        }
    });
    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    int id = item.getItemId();
    if (id == R.id.action_search) {
        return true;
    }

    return super.onOptionsItemSelected(item);
}

@Override
public void onBackPressed() {
    if (!searchView.isIconified()) {
        searchView.setIconified(true);
        return;
    }
    super.onBackPressed();
}}

my xml file for item:

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_height="match_parent"
    android:layout_width="wrap_content"
  >

    <android.support.v7.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="160dp"
        android:orientation="horizontal"
        android:padding="8dp">

        <ImageView
            android:id="@+id/image"
            android:layout_width="100dp"
            android:layout_height="match_parent"
            android:layout_marginRight="8dp"
            android:scaleType="fitXY" />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <TextView
                android:id="@+id/title"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Title"
                android:textSize="16sp"
                android:textStyle="bold"
                />

    </LinearLayout>
    </android.support.v7.widget.CardView>
</LinearLayout>

Interface:

public interface ApiInterface {
@GET("search/shows?q=girls")
Call <List<Movie>> getMovies();
}

Client:

public class TvMazeApiClient {

    public static String BASE_URL ="http://api.tvmaze.com/";
    private static Retrofit retrofit;
    public static Retrofit getClient(){
        if(retrofit == null){
            retrofit = new Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
        }
        return retrofit;
    }

}

Thanks in advance for any help

The fields "name" and "image" that you are trying to parse are nested in a JSON object "show". You need to parse this field as well.

public interface ApiInterface {
    @GET("search/shows?q=girls")
    Call <List<MovieResponse>> getMovies();
}

Create a new Java object

public class MovieResponse {
    @SerializedName("show")
    @Expose
    private Movie show;

    public Movie getShow() {
        return show;
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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