Search code examples
androidlistviewandroid-listviewandroid-listfragment

Refreshing list in ListFragment


I am trying to create a list of movies with ListFragment (by following this tutorial). In the fragment I have a Loader and an AsyncTaskLoader which downloads information from a web service. The AsyncTaskLoader downloads all information correctly and the method "onLoadFinished" is executed correctly. In onLoadFinished I call a function in the listadapter to set the new data and then call notifyDataSetChanged(). I know that the data in the adapter is set correctly (log statement will print the title of the first movie from the adapter-class), but the listview won't show anything. However, if I supply a list of movies to the constructor of the adapter it works.

Code for the ListFragment (not all code, only the code that sets up the adapter, loader etc):

public class MovieListFragment extends ListFragment implements LoaderManager.LoaderCallbacks<List<MovieInformation>>{

  public static final String TAG = "MovieListFragment";
  private SimpleMovieListAdapter listAdapter;

  public MovieListFragment() {
  }

  @Override public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);

    System.out.println("DataListFragment.onActivityCreated");

    setEmptyText("No results");
    listAdapter = new SimpleMovieListAdapter(getActivity(), R.layout.movie_list_row_layout);
    setListAdapter(listAdapter);
    setListShown(false);
    getLoaderManager().initLoader(0, null, this);
}

The loader:

public Loader<List<MovieInformation>> onCreateLoader(int arg0, Bundle arg1) {
    Log.d(TAG, "onCreateLoader");
    return new MovieListDownloader(getActivity());
}

@Override
public void onLoadFinished(Loader<List<MovieInformation>> arg0,
        List<MovieInformation> data) {
    listAdapter.setData(data);
    Log.d(TAG, "onLoadFinished");
    Log.d(TAG, ("length is: " + data.size()));
    setListShown(true);
    /*
    if(data != null && !data.isEmpty()){
        setListShown(true);
    }else{
        setListShownNoAnimation(true);
    }
    */
}

@Override
public void onLoaderReset(Loader<List<MovieInformation>> arg0) {
    Log.d(TAG, "onLoaderReset");
    listAdapter.setData(null);
}

The Adapter:

public class SimpleMovieListAdapter extends ArrayAdapter<MovieInformation> {
  private static final String TAG = "SimpleMovieListAdapter";
  private Context context;
  private List<MovieInformation> movies;
  ImageLoader imageLoader;

  public SimpleMovieListAdapter(Context context, int textViewResourceId,
        List<MovieInformation> objects) {
      super(context, textViewResourceId, objects);
      this.context = context;
      this.movies = objects;
      imageLoader =  ImageLoader.getInstance();

  }
  public SimpleMovieListAdapter(Context context, int textViewResourceId){
      super(context, textViewResourceId);
      this.context = context;
      imageLoader =  ImageLoader.getInstance();
  }

  public void setData(List<MovieInformation> movies){
      this.movies = movies;

      //This log statement will display the title of the first movie in the set (so the list of movies is updated correctly)
      Log.d(TAG, this.movies.get(0).getTitle());
      notifyDataSetChanged();
  }

  public View getView(int position, View convertView, ViewGroup parent){
    Log.d(TAG, ("GetView position" + position));
    View rowView;
    LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    if(convertView == null){
        rowView = inflater.inflate(R.layout.movie_list_row_layout, parent, false);
    } else {
        rowView = convertView;
    }

    TextView titleText = (TextView) rowView.findViewById(R.id.movie_list_title);
    TextView yearText = (TextView) rowView.findViewById(R.id.movie_list_year);
    TextView voteAverageText = (TextView) rowView.findViewById(R.id.movie_list_vote_average);
    ImageView posterView = (ImageView) rowView.findViewById(R.id.movie_list_poster);
    titleText.setText(movies.get(position).getTitle());
    yearText.setText("(" + movies.get(position).getYear() + ")");
    voteAverageText.setText(movies.get(position).getFormattedRatingAndVoteCount() + " " + 
            context.getString(R.string.movie_info_votes) + ")");


    imageLoader.displayImage((MyApplication.POSTER_SOURCE_ADDRESS + 
            movies.get(position).getPosterPath()), posterView, MyApplication.options);

    return rowView;
}
}

Edit 1 I found a solution right after posting the question, but I dont think it's a "nice" solution. In onLoadFinished, instead of passing the data to the adapter.setData() I simply created a new adapter (passing in the array of movies to the constructor) and then called setListAdapter() again. It works, but it feels like an ugly solution (creating and setting new adapters probably would consume more resources?).

Edit 2 Just to clarify something about setData in the adapterclass: After adding all the data to the array (this.movies) I loop through the entire array and print the title for each movie with a Log-statement. So the data is stored in the arraylist in the adapterclass after calling setData, but the listview is still not populated with data.

public void setData(List<MovieInformation> movies){
         this.movies = movies;

    if(movies != null){
        this.movies.addAll(movies);

    }
    for(MovieInformation m : this.movies){
        Log.d(TAG, ("movie : " + this.movies.indexOf(m) + " " + this.movies.get(this.movies.indexOf(m)).getTitle()));
    }
    notifyDataSetChanged();
}

Solution

  • try this:

    instead of doing

      public void setData(List<MovieInformation> movies){
          this.movies = movies;
    
          //This log statement will display the title of the first movie in the set (so the list of movies is updated correctly)
          Log.d(TAG, this.movies.get(0).getTitle());
          notifyDataSetChanged();
      }
    

    change this line this.movies = movies;

    to

    this.movies.clear(); 
    if(movies!=null){
    
    this.movies.addAll(movies); }
    
    notifyDataSetChanged();
    

    EDIT

    When you are creating your listview you are using this constructor:

      public SimpleMovieListAdapter(Context context, int textViewResourceId){
      super(context, textViewResourceId);
      this.context = context;
      imageLoader =  ImageLoader.getInstance();
    

    }

    you are not creating a new instance of your data list try adding the line : this.movies = new ArrayList<MovieInformation>();

    and add the other fix, it should work

    EDIT #2 change your adapter to extend base adapter, change your constructors like this:

    and update the line where you create a new adapter accordingly

    public class SimpleMovieListAdapter extends BaseAdapter {
      private static final String TAG = "SimpleMovieListAdapter";
      private Context context;
      private List<MovieInformation> movies;
      ImageLoader imageLoader;
    
      public SimpleMovieListAdapter(Context context, List<MovieInformation> objects) {
          this.context = context;
          this.movies = objects;
          imageLoader =  ImageLoader.getInstance();
    
      }
      public SimpleMovieListAdapter(Context context){
          this.context = context;
          this.movies = new ArrayList<MovieInformation>();
          imageLoader =  ImageLoader.getInstance();
      }