David
David

Reputation: 195

Custom adapter not updating gridview

I am writing an app to pull movie data from the MovieDB api and display the information in a GridView.

When the app first loads I would expect the view to populate initially, but it does not. I have a sort option in the menu bar and when the sort option is set the first time the GridView is populated by movies in order of popularity, as it should initially, but regardless of what sort criteria is actually selected.

I have used the logs to determine that the correct data is being retrieved from the API and being processed properly, so I have to assume that the adapter is not updating the view properly.

Why isn't the view showing initially or updating as it should?

FilmFragment.java:

public class FilmFragment extends Fragment {

private ArrayList<FilmParcelable> filmParcels = new ArrayList<FilmParcelable>();

private ImageAdaptor mFilmAdaptor;

protected String[] sortOptions = {
        "popularity.desc",
        "vote_average.desc"
};

protected String sortBy = sortOptions[0];

private final String LOG_TAG = FilmFragment.class.getSimpleName();

public FilmFragment() {
}

@Override
public void onCreate(Bundle savedInstanceState){

    super.onCreate(savedInstanceState);

    if (savedInstanceState == null || !savedInstanceState.containsKey("films")){
        updateFilms();
        mFilmAdaptor = new ImageAdaptor(getActivity(),filmParcels);
    } else {
        filmParcels = savedInstanceState.getParcelableArrayList("films");
        mFilmAdaptor = new ImageAdaptor(getActivity(),filmParcels);
    }
    // allow fragment to handle menu events
    setHasOptionsMenu(true);
}

@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
    inflater.inflate(R.menu.filmfragment, menu);
}

public boolean onOptionsItemSelected(MenuItem item){
    //Handle action bar item clicks. The action bar will
    //automatically handle clicks on the Home/Up button, so long
    //as you specify a parent activity in AndroidManifest.xml
    int id = item.getItemId();
    if (id == R.id.action_sort){
        showSortDialog();
        return true;
    }

    return super.onOptionsItemSelected(item);
}

@Override
public void onSaveInstanceState(Bundle outState){
    outState.putParcelableArrayList("films", filmParcels);
    super.onSaveInstanceState(outState);
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {

    View rootView = inflater.inflate(R.layout.fragment_main, container, false);

    // Find GridView to populate with poster images
    GridView gridView = (GridView) rootView.findViewById(R.id.gridView);

    // Set the adaptor of the GridView to my ImageAdaptor
    gridView.setAdapter(mFilmAdaptor);
    updateAdaptor();

    return rootView;
}

// Update movie data in case there is a change in the "sort by" option
// Or the fragment is started with no saved data

public void updateFilms(){
    new FetchFilmTask().execute();
}

public void updateAdaptor(){
    mFilmAdaptor.clear();
    mFilmAdaptor.addAll(filmParcels);
    mFilmAdaptor.notifyDataSetChanged();
}

// Show dialog sort pop up
public void showSortDialog(){
    DialogFragment dialog = new SortDialog();
    dialog.setTargetFragment(this, 0);
    dialog.show(getActivity().getSupportFragmentManager(), "SortDialog");
}

// If a fragment or activity called by this fragment returns to this fragment,
// Get the information returned via the intent
public void onActivityResult(int requestCode, int resultCode, Intent data){

   if (requestCode == 0){
       int mSelected = data.getIntExtra("Selected Option", -1);
       if (mSelected != -1){
           sortBy = sortOptions[mSelected];
           updateFilms();
           updateAdaptor();
       }
   }
}


// Class to get JSON data from The Movie Database API
public class FetchFilmTask extends AsyncTask<Void, Void, FilmParcelable[]> {

    private final String LOG_TAG = FetchFilmTask.class.getSimpleName();

    private final String MOVIE_DB_API_KEY = "e1968ef8ba074d7d5bf07a59de8b2310";

    protected FilmParcelable[] doInBackground(Void... params){
        // These two need to be declared outside the try/catch
        // so that they can be closed in the finally block.
        HttpURLConnection urlConnection = null;
        BufferedReader reader = null;

        // Will contain raw JSON response as a string
        String movieDBStr = null;

        try {
            // Construct URL for Movie DB query
            Uri.Builder builder = new Uri.Builder();
            builder.scheme("http")
                    .authority("api.themoviedb.org")
                    .appendPath("3")
                    .appendPath("discover")
                    .appendPath("movie")
                    .appendQueryParameter("api_key", MOVIE_DB_API_KEY)
                    .appendQueryParameter("sort_by", sortBy);


            String myUrl = builder.build().toString();
            Log.d(LOG_TAG, myUrl);

            URL url = new URL(myUrl);

            // Create the request to The Movie DB, and open the connection
            urlConnection = (HttpURLConnection) url.openConnection();
            urlConnection.setRequestMethod("GET");
            urlConnection.connect();

            // Read the input stream into a String
            InputStream inputStream = urlConnection.getInputStream();
            StringBuffer buffer = new StringBuffer();

            if (inputStream == null){
                return null;
            }

            reader = new BufferedReader(new InputStreamReader(inputStream));

            String line;
            while ((line = reader.readLine()) != null) {
                // Since it's JSON, adding a newline isn't necessary (it won't affect parsing)
                // But it does make debugging a *lot* easier if you print out the completed
                // buffer for debugging.
                buffer.append(line + "\n");
            }

            if (buffer.length() == 0) {
                return null;
            }
            movieDBStr = buffer.toString();
        } catch (IOException e){
            Log.e(LOG_TAG, "Error: ", e);
            return null;
        } finally {
            if (urlConnection != null){
                urlConnection.disconnect();
            }
            if (reader != null){
                try{
                    reader.close();
                } catch (final IOException e){
                    Log.e(LOG_TAG, "Error closing stream", e);
                }
            }
        }
        try {
            return getFilmDataFromJson(movieDBStr);
        } catch (JSONException e){
            Log.e(LOG_TAG, e.getMessage(), e);
            e.printStackTrace();
        }

        return null;
    }

    /**
     * Take the String representing the complete forecast in JSON Format and
     * pull out the data we need to construct the Strings needed for the wireframes.
     *
     * Fortunately parsing is easy:  constructor takes the JSON string and converts it
     * into an Object hierarchy for us.
     */
    private FilmParcelable[] getFilmDataFromJson(String movieDBStr)
        throws JSONException {

        // JSON objects that need to be extracted
        final String MDB_RESULTS = "results";
        final String MDB_ID = "id";
        final String MDB_SYNOPSIS = "overview";
        final String MDB_RELEASE = "release_date";
        final String MDB_POSTER = "poster_path";
        final String MDB_TITLE = "title";
        final String MDB_RATING = "vote_average";

        JSONObject filmJson = new JSONObject(movieDBStr);
        JSONArray filmArray = filmJson.getJSONArray(MDB_RESULTS);

        FilmParcelable[] resultFilms = new FilmParcelable[filmArray.length()];
        for (int i = 0; i < filmArray.length(); i++){
            // Data needed by the FilmParcelable
            int id;
            String title;
            String releaseDate;
            String posterUrl;
            Double voteAverage;
            String plotSynopsis;

            JSONObject film = filmArray.getJSONObject(i);

            id = film.getInt(MDB_ID);
            plotSynopsis = film.getString(MDB_SYNOPSIS);
            releaseDate = film.getString(MDB_RELEASE);
            posterUrl = "http://image.tmdb.org/t/p/w300" + film.getString(MDB_POSTER);
            title = film.getString(MDB_TITLE);
            voteAverage = film.getDouble(MDB_RATING);
            Log.d(LOG_TAG, title);
            Log.d(LOG_TAG, posterUrl);

            resultFilms[i] = new FilmParcelable(id, title, releaseDate, posterUrl, voteAverage, plotSynopsis);
        }

        return resultFilms;
    }

    @Override
    protected void onPostExecute(FilmParcelable[] result){
        if (result != null){
            filmParcels = new ArrayList<>(Arrays.asList(result));
        }
    }

}

}

ImageAdaptor.java:

public class ImageAdaptor extends ArrayAdapter<FilmParcelable> {

public ImageAdaptor(Activity context, ArrayList<FilmParcelable> filmParcels){
    super(context, 0, filmParcels);
}

public View getView(int position, View convertView, ViewGroup parent){

    Context context= getContext();
    View gridView;

    LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

    String mUrl = getItem(position).getUrl();

    if (convertView == null) {

        gridView = inflater.inflate(R.layout.gridview_film_layout, parent, false);

        // Find the image view from the gridview_film_layout
        ImageView posterView = (ImageView) gridView.findViewById(R.id.grid_item_image);

        // Set the image view to contain image located at mUrl
        Picasso.with(getContext()).load(mUrl).into(posterView);
    } else {
        gridView = convertView;
    }

    return gridView;
}

}

Upvotes: 0

Views: 220

Answers (2)

David
David

Reputation: 195

I was attempting to update the adapter after populating the arraylist used for the adapter. However the arraylist was populated and updated in the background so the code:

updateFilms();
updateAdaptor();

was causing the adaptor to update before the data had completed loading in the background.

After fixing that Blackbelt's solution was correct.

Upvotes: 0

Blackbelt
Blackbelt

Reputation: 157437

accordingly to the code you posted you are returning over and over the same cell of your GridView. You should have those two lines

ImageView posterView = (ImageView) gridView.findViewById(R.id.grid_item_image);
Picasso.with(getContext()).load(mUrl).into(posterView);

out of the if/else guard:

 if (convertView == null) {
    // inflate
 } else {
   // gridView = convertView;
 }

  ImageView posterView = (ImageView) gridView.findViewById(R.id.grid_item_image);
  Picasso.with(getContext()).load(mUrl).into(posterView);
  return gridView;

Upvotes: 1

Related Questions