Giter Site home page Giter Site logo

Android RecyclerView IllegalArgumentException: called detach on an already detached child ViewHolder about recycler-view-merge-adapter HOT 13 CLOSED

guruduttstay avatar guruduttstay commented on July 28, 2024
Android RecyclerView IllegalArgumentException: called detach on an already detached child ViewHolder

from recycler-view-merge-adapter.

Comments (13)

guruduttstay avatar guruduttstay commented on July 28, 2024 1

Hi @ronaldw

Thank you for your help.

If you'd be willing to try out this code it would be a tremendous help.

Sure I will try this

JFYI : I fond this crash more often on appcompat-v7:23.4.0 and heigher version

I have seen this issue only 2 times with appcompat-v7:23.0.1 .... not sure if that is something you want to look into

I will try your suggestion and get back to you asap

let me know how can I try your merged branch

Thank you

from recycler-view-merge-adapter.

ronaldwolvers avatar ronaldwolvers commented on July 28, 2024

Hi, can you please copy and past the code snippet(s) in which you instantiate the adapter, add the sub-adapters to the adapter and where you possibly notify the adapter of data changes? This would be a tremendous help.

from recycler-view-merge-adapter.

guruduttstay avatar guruduttstay commented on July 28, 2024

Hi Ronaldw

Thank you for response

I have simplified my code and changed it a bit, changed function and variable names and logic to a bit but code looks like this


    private void updateMyAdapter() {

        ModelA modelA = CManager.getInstance().getModel(mAModelID);
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);

        if (recyclerView == null || modelA == null) {
            return;
        }

        Activity activity = getActivity();
        RecyclerViewMergeAdapter adapter = new RecyclerViewMergeAdapter();
        recyclerView.setAdapter(adapter);

        if (mImageHeader != null) { 

// NB : mImageHeader is created onCreateView =>  View mImageHeader = inflater.inflate(R.layout.header_image, recyclerView, false);


            adapter.addView(mImageHeader);
        }

        View modelHeading = null;

        List<ModelB> favModelBs = CManager.getInstance().getModelBForModelA(mAModelID);

        if (favModelBs != null && !favModelBs.isEmpty() && activity != null) {
            MyRecyclerAdapter myRecyclerAdapter = new MyRecyclerAdapter(favModelBs, mAModelID, DisplayMode.MODEL_HINT);
            myRecyclerAdapter.setItemClickListener(this);
            modelHeading = inflateListLabel(recyclerView, getResources().getString(R.string.modelHeading), true); // inflateListLabel function inflate layout
            adapter.addView(modelHeading);
            adapter.addAdapter(myRecyclerAdapter);
        }

        List<String> groupingIds = modelA.getGroupingIDs();

        if (groupingIds != null && !groupingIds.isEmpty()) {
            for (String groupingId : groupingIds) {
                GroupingModel grouping = CManager.getInstance().FetchGrouping(mAModelID, groupingId);
                if (grouping != null) {
                    List<ModelB> modelList = grouping.getModelBList();
                    if (modelList != null && !modelList.isEmpty() && activity != null) {
                        MyRecyclerAdapter myRecyclerAdapter = new MyRecyclerAdapter(modelList, mAModelID, DisplayMode.MODEL_HINT);
                        myRecyclerAdapter.setItemClickListener(this);
                        adapter.addView(inflateListLabel(recyclerView, grouping.getName(), updatePadding));
                        adapter.addAdapter(myRecyclerAdapter);
                    }
                }
            }
        }

        adapter.notifyDataSetChanged();
    }

And function call

    private View inflateListLabel(ViewGroup container, String string, boolean updatePadding) {
        LayoutInflater inflater = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View inflateView = inflater.inflate(R.layout.grouping_titles, container, false);
        if (inflateView != null) {
            TextView textView = (TextView) inflateView.findViewById(R.id.title_text);
            if (textView != null) {
                textView.setText(Html.fromHtml(string));
                if (updatePadding) {
                    RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
                    lp.setMargins(getResources().getDimensionPixelSize(R.dimen.marginTwo), getResources().getDimensionPixelSize(R.dimen.marginTwo), getResources().getDimensionPixelSize(R.dimen.marginTwo), getResources().getDimensionPixelSize(R.dimen.marginFour));
                    textView.setLayoutParams(lp);
                }
            }
        }
        return inflateView;
    }




from recycler-view-merge-adapter.

ronaldwolvers avatar ronaldwolvers commented on July 28, 2024

Hi, @guruduttstay. The reason I am asking is because I have seen this problem occur before in my own project when I was not using the correct notify<operation>() method or I was calling notifyDataSetChanged() when actually my activity already had its View-s destroyed. Are you calling this method from a Fragment? If so, I would recommend always checking whether you are attached to an Activity by calling isAdded() and making sure you are added before notifying.

Furthermore, I would recommend using notifyItemRangeInserted() instead of notifyDataSetChanged(). For instance when you add an adapter by calling adapter.addAdapter(mRecyclerAdapter) you can call adapter.notifyItemRangeInserted(adapter.getItemCount() - 1 - mRecyclerAdapter.getItemCount(), adapter.getItemCount() - 1). This way you are informing the adapter that you have inserted some new items and using a less heavy operation than the RecyclerView having to recalculate everything.

Meanwhile, we have just merged a Pull Request that has quite drastically overhauled the source code for the adapter. If you'd be willing to try out this code it would be a tremendous help. I have been using the adapter in this way in my own project for a very long time and it works fantastically. Hope you'll fix the crash.

from recycler-view-merge-adapter.

ronaldwolvers avatar ronaldwolvers commented on July 28, 2024

The changes have been merged to master just now so just pull from this repo and use the new RecyclerViewMergeAdapter. It should be mostly backwards compatible. I personally rarely use the appcompat libraries, so I have no clue as to whether that could be a factor. I am currently just creating a simply activity and adding views to it using addView() and then clearing them using the new call clearAdapters. Are you using the code here or the library on bintray?

from recycler-view-merge-adapter.

ronaldwolvers avatar ronaldwolvers commented on July 28, 2024

Just now we have published version 2.0.0 on bintray. Use:

compile 'me.mvdw.recyclerviewmergeadapter:recyclerviewmergeadapter:2.0.0'

If you find any issues with the adapter, please file another issue. I'd be happy to help you solve any bugs if you come across them.

from recycler-view-merge-adapter.

ronaldwolvers avatar ronaldwolvers commented on July 28, 2024

Finally, if you are curious about how to properly use the adapter and the notify<operation>() calls, I have just created a very small app that serves to simply test RecyclerViewMergeAdapter's capabilities. Link: https://github.com/ronaldw/RecyclerViewMergeAdapterTest

from recycler-view-merge-adapter.

guruduttstay avatar guruduttstay commented on July 28, 2024

I am still getting this issue :(

from recycler-view-merge-adapter.

guruduttstay avatar guruduttstay commented on July 28, 2024

Hi

I have updated my code with updated RecyclerViewMergeAdapter (2.x)
and it gives better results but still in some rare cases it crashes with appCompat 23.0.1

but with appCompat 23.4.0 and higher versions still it I get this issue more often

private void updateMyAdapter() {

        ModelA modelA = CManager.getInstance().getModel(mAModelID);
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);

        if (recyclerView == null || modelA == null) {
            return;
        }

        Activity activity = getActivity();

       if (recyclerView.getAdapter() == null) {
          recyclerView.setAdapter(new RecyclerViewMergeAdapter());
       } else {
           clearAdapters();
       }


        if (mImageHeader != null) { 
            // NB : mImageHeader is created onCreateView =>  View mImageHeader = inflater.inflate(R.layout.header_image, recyclerView, false);
            appendView(mImageHeader);
        }

        View modelHeading = null;

        List<ModelB> favModelBs = CManager.getInstance().getModelBForModelA(mAModelID);

        if (favModelBs != null && !favModelBs.isEmpty() && activity != null) {
            MyRecyclerAdapter myRecyclerAdapter = new MyRecyclerAdapter(favModelBs, mAModelID, DisplayMode.MODEL_HINT);
            myRecyclerAdapter.setItemClickListener(this);
            modelHeading = inflateListLabel(recyclerView, getResources().getString(R.string.modelHeading), true); // inflateListLabel function inflate layout
            if (isAdded()) {
                appendView(modelHeading);
                myRecyclerAdapter.notifyDataSetChanged();
                appendAdapter(myRecyclerAdapter);
            }
        }

        List<String> groupingIds = modelA.getGroupingIDs();

        if (groupingIds != null && !groupingIds.isEmpty()) {
            for (String groupingId : groupingIds) {
                GroupingModel grouping = CManager.getInstance().FetchGrouping(mAModelID, groupingId);
                if (grouping != null) {
                    List<ModelB> modelList = grouping.getModelBList();
                    if (modelList != null && !modelList.isEmpty() && activity != null) {
                        MyRecyclerAdapter myRecyclerAdapter = new MyRecyclerAdapter(modelList, mAModelID, DisplayMode.MODEL_HINT);
                        myRecyclerAdapter.setItemClickListener(this);
                        if (isAdded()) {
                            appendView(inflateListLabel(recyclerView, grouping.getName(), updatePadding));
                            myRecyclerAdapter.notifyDataSetChanged();
                            appendAdapter(myRecyclerAdapter);
                        }
                    }
                }
            }
        }

       RecyclerViewMergeAdapter adapter = (RecyclerViewMergeAdapter) recyclerView.getAdapter();
       if (isAdded() && adapter != null) {
           adapter.notifyDataSetChanged();
       }
    }

private void appendAdapter(MyRecyclerAdapter myRecyclerAdapter) {
    
    RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);

    if (recyclerView != null) {
        
        RecyclerViewMergeAdapter adapter = (RecyclerViewMergeAdapter) recyclerView.getAdapter();

        if (adapter != null) {

             adapter.addAdapter(myRecyclerAdapter);

        }
    
    }

}




private void appendView(View view) {

    RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);

    if (recyclerView != null && view != null) {

        RecyclerViewMergeAdapter adapter = (RecyclerViewMergeAdapter) recyclerView.getAdapter();

        if (adapter != null) {

            adapter.addView(view);

        }

    }

}



private void clearAdapters() {

    RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);

    if (recyclerView != null && isAdded()) {

        RecyclerViewMergeAdapter recyclerViewMergeAdapter = (RecyclerViewMergeAdapter) recyclerView.getAdapter();

        recyclerViewMergeAdapter.clearAdapters();

        int itemCountToBeCleared = recyclerViewMergeAdapter.getItemCount();

        recyclerViewMergeAdapter.notifyItemRangeRemoved(0, itemCountToBeCleared);

    }

}




from recycler-view-merge-adapter.

ronaldwolvers avatar ronaldwolvers commented on July 28, 2024

Hi @guruduttstay . Sorry that these problems keep bothering you. The RecyclerView is a rather complex component so it is not easy for me to say what is causing the exception immediately. However, I did notice in your code that you call notifyItemRangeRemoved() with itemCountToBeCleared set to 0. After you call clearAdapters() the adapter will return 0 for getItemCount() hence you would have to call it before you clear it.

Also, when you call adapter.addView(), make sure you call notifyItemRangeInserted(). A single adapter is inserted when this happens, but you always have to call this method for as many items as are there are in your sub adapter. I will look into possible causes of this issue today, but I have had it myself before and it was caused by not calling the correct notify<operation>() methods.

from recycler-view-merge-adapter.

ronaldwolvers avatar ronaldwolvers commented on July 28, 2024

Hi again,

To the best of my efforts I have tried to change your code to have it include the correct notify<operation>() at the right moment:


    private void updateMyAdapter() {

        ModelA modelA = CManager.getInstance().getModel(mAModelID);
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);

        if (recyclerView == null || modelA == null) {
            return;
        }

        Activity activity = getActivity();

        if (recyclerView.getAdapter() == null) {

            recyclerView.setAdapter(new RecyclerViewMergeAdapter());
        } else {
            
clearAdapters();

        }


        if (mImageHeader != null) {
            // NB : mImageHeader is created onCreateView =>  View mImageHeader = inflater.inflate(R.layout.header_image, recyclerView, false);
            appendView(mImageHeader);
        }

        View modelHeading = null;

        List<ModelB> favModelBs = CManager.getInstance().getModelBForModelA(mAModelID);

        if (favModelBs != null && !favModelBs.isEmpty() && activity != null) {
            MyRecyclerAdapter myRecyclerAdapter = new MyRecyclerAdapter(favModelBs, mAModelID, DisplayMode.MODEL_HINT);
            myRecyclerAdapter.setItemClickListener(this);
            modelHeading = inflateListLabel(recyclerView, getResources().getString(R.string.modelHeading), true); // inflateListLabel function inflate layout
            if (isAdded()) {
appendView(modelHeading);
myRecyclerAdapter.notifyDataSetChanged();

                appendAdapter(myRecyclerAdapter);
}
        }

        List<String> groupingIds = modelA.getGroupingIDs();

        if (groupingIds != null && !groupingIds.isEmpty()) {
            for (String groupingId : groupingIds) {
                GroupingModel grouping = CManager.getInstance().FetchGrouping(mAModelID, groupingId);
                if (grouping != null) {
                    List<ModelB> modelList = grouping.getModelBList();
                    if (modelList != null && !modelList.isEmpty() && activity != null) {
                        MyRecyclerAdapter myRecyclerAdapter = new MyRecyclerAdapter(modelList, mAModelID, DisplayMode.MODEL_HINT);
                        myRecyclerAdapter.setItemClickListener(this);
                        if (isAdded()) {

                            appendView(inflateListLabel(recyclerView, grouping.getName(), updatePadding));

                            // DO NOT call notifyDataSetChanged() unless absolutely necessary!
//                            myRecyclerAdapter.notifyDataSetChanged();

                            appendAdapter(myRecyclerAdapter);
}
                    }
                }
            }
        }

        RecyclerViewMergeAdapter adapter = (RecyclerViewMergeAdapter) recyclerView.getAdapter();

        if (isAdded() && adapter != null) {
adapter.notifyDataSetChanged();
}
    }

    private void appendAdapter(MyRecyclerAdapter myRecyclerAdapter) {

        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);

        if (recyclerView != null) {

            RecyclerViewMergeAdapter adapter = (RecyclerViewMergeAdapter) recyclerView.getAdapter();

            if (adapter != null) {

                adapter.addAdapter(myRecyclerAdapter);

                adapter.notifyItemInserted(adapter.getItemCount());
            }

        }
    }

    private void appendView(View view) {

        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);

        if (recyclerView != null && view != null) {

            RecyclerViewMergeAdapter adapter = (RecyclerViewMergeAdapter) recyclerView.getAdapter();
            
if (adapter != null) {

                adapter.addView(view);

                adapter.notifyItemInserted(adapter.getItemCount());
            }

        }
    }

    



    private void clearAdapters() {

        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        
if (recyclerView != null && isAdded()) {
            RecyclerViewMergeAdapter recyclerViewMergeAdapter = (RecyclerViewMergeAdapter) recyclerView.getAdapter();
            int itemCountToBeCleared = recyclerViewMergeAdapter.getItemCount();

            
recyclerViewMergeAdapter.clearAdapters();

            recyclerViewMergeAdapter.notifyItemRangeRemoved(0, itemCountToBeCleared);

        }
    }

I have modified your clearAdapters() method, removed notifyDataSetChanged() and added notifyItemRangeInserted() to your methods appendView() and appendAdapter(). Could you try it out and let me know how it behaves? I have been looking around the Web and multiple sources report that the IllegalStateException you keep encountering is due to not calling the correct notification method. Thanks and I hope you'll be able to make the merge adapter work!

from recycler-view-merge-adapter.

guruduttstay avatar guruduttstay commented on July 28, 2024

Thank you @ronaldw for your great help :)

Finally we figure it out and fixed it.

I am using viewPager and in next fragment I had this code where it was adding same view twice ...
and I didn't focused in that fragment at all and looking in current active tab in ViewPager and took lot of time to find actual culprit :(

View separator = Utils.inflateViewLabel(getActivity(), recyclerView ………);
if(separator != null) {
    mergeAdapter.addView(separator);
    mergeAdapter.addView(separator);
}

It added same view twice to mergeAdapter and ...... :(

from recycler-view-merge-adapter.

freecsdn avatar freecsdn commented on July 28, 2024

I meet the same Issue, but it does not solve my problem.Is there any answer?

from recycler-view-merge-adapter.

Related Issues (9)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.