34

I am using RecyclerView in my android project and its performance has been really awful. From the answers here, I tried adding adapter.setHasStableIds(true); to my code. When running, I got an error:

java.lang.IllegalStateException: Cannot change whether this adapter has stable IDs while the adapter has registered observers.

My full logCat is as below;

09-22 22:22:23.634 1808-1808/com.revosleap.movielist E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.revosleap.movielist, PID: 1808
java.lang.IllegalStateException: Cannot change whether this adapter has stable IDs while the adapter has registered observers.
    at android.support.v7.widget.RecyclerView$Adapter.setHasStableIds(RecyclerView.java:6749)
    at com.revosleap.movielist.Utils.UrlUtils.GenreFetcher.getGenre(GenreFetcher.java:48)
    at com.revosleap.movielist.MainActivity$2.onItemSelected(MainActivity.java:221)
    at android.widget.AdapterView.fireOnSelected(AdapterView.java:1124)
    at android.widget.AdapterView.access$200(AdapterView.java:54)
    at android.widget.AdapterView$SelectionNotifier.run(AdapterView.java:1089)
    at android.os.Handler.handleCallback(Handler.java:739)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:145)
    at android.app.ActivityThread.main(ActivityThread.java:5942)
    at java.lang.reflect.Method.invoke(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:372)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1400)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1195)

How can I fix this please?

Carlos Anyona
  • 641
  • 1
  • 7
  • 17

4 Answers4

72

You must set the adapter's hasStableIds to true BEFORE you assign the adapter to your recyclerview.

YourAdapter adapter = new YourAdapter();
adapter.setHasStableIds(true);
myRecyclerView.setAdapter(adapter);
iqbalzas
  • 767
  • 7
  • 12
12

as the recyclerView source code:

    /**
     * Indicates whether each item in the data set can be represented with a unique identifier
     * of type {@link java.lang.Long}.
     *
     * @param hasStableIds Whether items in data set have unique identifiers or not.
     * @see #hasStableIds()
     * @see #getItemId(int)
     */
    public void setHasStableIds(boolean hasStableIds) {
        if (hasObservers()) {
            throw new IllegalStateException("Cannot change whether this adapter has "
                    + "stable IDs while the adapter has registered observers.");
        }
        mHasStableIds = hasStableIds;
    } .   

so you can workaround it .

    if (!adapter.hasObservers()) {
        adapter.setHasStableIds(true)
    }     
gavin gu
  • 121
  • 3
0

You can not use adapter.setHasStableIds(true); with ObservableArrayList/ObservableFields

Andreas
  • 255
  • 3
  • 16
0

In my case, it's because the I put the setHasStableIds(true) inside the Fragment's onActivityCreated(). The Fragment is the main point of my application so it never gets popped from back stack.

class LocalFragment: Fragment() {
    private val adapter = LocalAdapter()
 
    override fun onActivityCreated(savedInstanceState: Bundle?) {
        adapter.setHasStableIds(true)
    }
}

The problem here is that the onActivityCreated() is called every time the Fragment is navigated to. However, as the Fragment is still alive in the back stack, the private val adapter field is still the same object that had its setStableIds(true) called previously.

class LocalFragment: Fragment() {
    private var adapter = LocalAdapter()
 
    override fun onActivityCreated(savedInstanceState: Bundle?) {
        adapter = LocalAdapter() // Re-assign the adapter
        adapter.setHasStableIds(true)
    }
}

Solution is to reassign the RecyclerView.Adapter object every time the fragment is switched to.

flamyoad
  • 519
  • 7
  • 15