0

I'm trying to replace items in my RecyclerView when someone searches for a word. A list of GIFs is sent to my callback method, and I'm looping through those objects, and wanting to replace each one in the RecyclerView. However, when I try to do this, I get an IndexOutOfBoundsException if I call ArrayList.set() in my adapter.

I get why it's happening...the ArrayList in my adapter has zero items in it. My question is why? What am I doing wrong?

My activity code:

package hidden.package;

// Imports hidden

public class GifSearchActivity extends AppCompatActivity {

    @BindView(R.id.gifSearchBar)
    FloatingSearchView searchGifs;

    @BindView(R.id.gifSearchResults)
    RecyclerView searchResults;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_gifsearch);
        ButterKnife.bind(this);

        final GIFResultsAdapter adapter = new GIFResultsAdapter();

        final SharedPreferences prefs = PreferenceManager
                .getDefaultSharedPreferences(GifSearchActivity.this);

        final GIFInterface i = new GIFInterface();
        i.setSearchListener(new GIFInterface.GIFSearchListener() {
            @Override
            public void onSearchResultsRetrieved(final GIFSearchResults gifSearchResults) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Log.d("GAB", "Results Retrieved...");
                        int count = 0;
                        for (GIF gif : gifSearchResults.getGIFs()) {
                            adapter.replaceItem(gif, count);
                            count++;
                        }
                    }
                });
            }
        });

        searchResults.setLayoutManager(new GridLayoutManager(getBaseContext(), 1, GridLayoutManager.VERTICAL, false));
        searchResults.setItemAnimator(new DefaultItemAnimator());
        searchResults.setAdapter(adapter);

        searchGifs.setOnQueryChangeListener(new FloatingSearchView.OnQueryChangeListener() {
            @Override
            public void onSearchTextChanged(String oldQuery, String newQuery) {
                i.searchForGif(prefs.getString("token", ""), newQuery);
            }
        });
    }
}

My Adapter code:

package hidden.package;

// Imports hidden

@SuppressWarnings("deprecation")
public class GifResultsAdapter extends RecyclerView.Adapter<GifResultsAdapter.GifsViewHolder> {

    private ArrayList<GIF> gifs = new ArrayList<>();
    private Context con;

    public class GifsViewHolder extends RecyclerView.ViewHolder {

        ImageView gif;
        ProgressBar gifProgressBar;

        public GifsViewHolder(View itemView) {
            super(itemView);
            gif = (ImageView) itemView.findViewById(R.id.gif);
            gifProgressBar = (ProgressBar) itemView.findViewById(R.id.gifProgressBar);
        }
    }

    public void replaceItem(GIF gif, int position) {
        gifs.set(position, gif);
        notifyItemChanged(position);
    }

    @Override
    public GifsViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        Log.d("GAB", "ViewHolder Created");
        View itemView = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.util_gif_row, parent, false);
        this.con = parent.getContext();
        return new GifsViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(final GifsViewHolder holder, int position) {
        Log.d("GAB", "ViewHolder bound");
        final GIF gif = gifs.get(position);
        Glide.with(con)
                .load(gif.getUrl())
                .override(700, 700)
                .fitCenter()
                .placeholder(new ColorDrawable(AndroidHelper.darkenColor(con.getResources().getColor(R.color.colorAccent), 0.95f)))
                .into(holder.gif);
    }

    @Override
    public int getItemCount() {
        return gifs.size();
    }
}

Edit: Here's the exception...

02-25 00:41:46.371 29205-29205/ai.gab.android E/UncaughtException: java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
  at java.util.ArrayList.set(ArrayList.java:427)
  at ai.gab.android.ui.adapter.GIFResultsAdapter.replaceItem(GIFResultsAdapter.java:59)
  at ai.gab.android.ui.activity.GifSearchActivity$1$1.run(GifSearchActivity.java:68)
  at android.os.Handler.handleCallback(Handler.java:751)
  at android.os.Handler.dispatchMessage(Handler.java:95)
  at android.os.Looper.loop(Looper.java:154)
  at android.app.ActivityThread.main(ActivityThread.java:6119)
  at java.lang.reflect.Method.invoke(Native Method)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)

A set of fresh eyes would be greatly appreciated!

Andrew Quebe
  • 2,263
  • 5
  • 25
  • 53
  • I should mention that, at first, I tried adding the GIFs to the RV...but that ended up adding the GIFs for each query the user made. So if I typed `angry`, it would load in gifs for the query `a`, `an`, `ang`, etc. That's why I want to replace the results. – Andrew Quebe Feb 25 '17 at 09:07
  • You create an empty list (`gifs`) of GIF objects. You need to pass your list in the constructor (for example) – GVillani82 Feb 25 '17 at 09:12

3 Answers3

0

the method you are calling is replaceItem it replaces an element with another one, in your case you're calling it when your list it empty, you should create at first a method to add Gifs in the adapter

public void addItem(GIF gif) {
    gifs.add(gif);
    notifyItemChanged(position);
}

and instead of calling replaceItem the first time you get a Gif you should call addItem

after that when you need to replace a specific element you could use replaceItem

Sofiane Daoud
  • 868
  • 9
  • 21
0

I would do something slightly different. I would wait for a small amount of time at each keypress, about a 50/100ms, and afetr that request a new content load (maybe using loaders).

In this way you can prevent to trigger a lot of probably exprensive and useless searches and after that replace all the content of your adapter usign the new result collection.

In the example below i will use an AsyncTask just to be fast

In your GifSearchActivity add a variable called gifSearchTask for the AsyncTask defined below and replace yout query change listener with this one.

In this way you will fire an event for every query change, cancelling the previous if is running or pending. start the new one with your new query and at the end replace the entire dataset of your recycler view with the new one.

searchGifs.setOnQueryChangeListener(new FloatingSearchView.OnQueryChangeListener() {
        @Override
        public void onSearchTextChanged(String oldQuery, String newQuery) {
            if (gifSearchTask != null && AsyncTask.Status.FINISHED != gifSearchTask.getStatus()) {
                gifSearchTask.cancel(true);
            }
            gifSearchTask = new GifSearchAsyncTask();
            gifSearchTask.execute(newQuery);
        }
    });


class GifSearchAsyncTask extends AsyncTask<String, Void, List<Gif>> {

    @Override
    protected List<Gif> doInBackground(String... params) {
        List<Gif> results = null;
        android.os.SystemClock.sleep(100);
        // if not cancelled
        if (!isCancelled()) {
            results = performSearch(params);
        }
        return results;
    }

    @Override
    protected void onCancelled() {
        // do nothing
    }

    @Override
    protected void onCancelled(Result result) {
        // do nothing
    }

    @Override
    protected void onPostExecute(List<Gif> results) {
        adapter.setData(results);
        gifSearchTask = null;
    }

    private List<Gif> performSearch(String ... params) {
        // perform search and retur the results
    }
}
Marco Capoferri
  • 224
  • 2
  • 12
0

I came up with a better solution: create a clear() method, and call that when the search query changes.

Special thanks to this SO answer. Now every time the search query is changed, the current items are removed, and the new ones are added in.

Community
  • 1
  • 1
Andrew Quebe
  • 2,263
  • 5
  • 25
  • 53