简体   繁体   中英

Add fragment in RecyclerView.ViewHolder

I have a RecyclerView.ViewHolder which will add different fragment into its FrameLayout based on the instance of the object passed. The problem comes where it is almost impossible to add fragment into the ViewHolder. Take note that I already passed the FragmentManager from the parent. Initially I try with this code

public void setSomething(boolean A) {
    if (A) {
         mFragmentManager.beginTransaction()
            .replace(mBinding.typeContainerLayout.getId(), new FragmentA())
            .commit();
    } else {
        mFragmentManager.beginTransaction()
            .replace(mBinding.typeContainerLayout.getId(), new FragmentB())
            .commit();
    }
}

The problem with this code is that all the ViewHolder share the same id, thus only a single ViewHolder can add the fragment. In my RecyclerView, only the first cell added the fragment. To tackle this problem, I create another FrameLayout and add it into typeContainerLayout . Now my code become like this.

public void setSomething(boolean A) {
    FrameLayout frameLayout = new FrameLayout(mContext);
    frameLayout.setId(View.generateViewId());
    mBinding.typeContainerLayout.removeAllViews();
    mBinding.typeContainerLayout.addView(frameLayout)

    if (A) {
         mFragmentManager.beginTransaction()
            .replace(frameLayout.getId(), new FragmentA())
            .commit();
    } else {
        mFragmentManager.beginTransaction()
            .replace(frameLayout.getId(), new FragmentB())
            .commit();
    }
}

Now each ViewHolder has added the fragment correctly and has their own fragment. However the problem comes when I added like 5 ViewHolder and trying to scroll down the RecyclerView, a runtime error occurred which state

java.lang.IllegalArgumentException: No view found for id 0x4 (unknown) for fragment FragmentA{7c55a69 #0 id=0x4 FragmentA}
                      at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1292)
                      at android.support.v4.app.FragmentManagerImpl.moveFragmentsToInvisible(FragmentManager.java:2323)
                      at android.support.v4.app.FragmentManagerImpl.executeOpsTogether(FragmentManager.java:2136)
                      at android.support.v4.app.FragmentManagerImpl.optimizeAndExecuteOps(FragmentManager.java:2092)
                      at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1998)
                      at android.support.v4.app.FragmentManagerImpl$1.run(FragmentManager.java:709)
                      at android.os.Handler.handleCallback(Handler.java:739)
                      at android.os.Handler.dispatchMessage(Handler.java:95)
                      at android.os.Looper.loop(Looper.java:148)
                      at android.app.ActivityThread.main(ActivityThread.java:5417)
                      at java.lang.reflect.Method.invoke(Native Method)
                      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
                      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)

My guess is that either the id conflicted at some point, or the view got destroyed due to the ViewHolder pattern. So my question is that.

1) Is there any workaround to it?

2) Is there any better practice than adding fragment. The reason I add fragment is so that the logic for the sub item of the ViewHolder can all be located in a single fragment. Of course I can just put both the views for the fragments into the ViewHolder xml. And just setVisible() depending on the condition. But that will just make my ViewHolder contain too many logic.

In case someone is confused why I need fragment. This is what I am trying to achieve. The image

Short answer: you shouldn't use fragments inside a recyclerView, that's not what they're intended for.

Long answer: here

You should know exactly that recyclerview created your holder and drew a view for him, so you need to attach a fragment in onViewAttachedToWindow method of your adapter. In your "attaching fragment" method you should check if fragment manager already contains those fragments to prevent of creating multiple instances.

Adapter:

 override fun onViewAttachedToWindow(holder: RecyclerView.ViewHolder) {    
    if(holder is FragmentsHolder){
        attachFragmentToContainer(holder.flContainer)            
    }
    super.onViewAttachedToWindow(holder)
}

Method realization:

 fun attachFragmentToContainer(containerId: Int) {
    val fragment = if (fragmentManager?.fragments?.firstOrNull { it is YourFragment } == null)
        YourFragment.instance()
    else
        null

    if (fragment != null) {
        fragmentManager?.beginTransaction()
            ?.add(containerId, fragment)
            ?.commitNowAllowingStateLoss()
    }
}

This tested on big amount of users - no crashes, good perfomance.

onBindViewHolder:

yourFragment fragment = new yourFragment();
    try {
        viewHolder.Fragment(fragment);
    }catch (Exception e){
        Toast.makeText(context, "BindViewHolder"+e.getMessage(), Toast.LENGTH_SHORT).show();
    }

ViewHolder:

class ViewHolder extends RecyclerView.ViewHolder {
    TextView name;
    FrameLayout container;

    private ViewHolder(@NonNull View itemView) {
        super(itemView);

        container = itemView.findViewById(R.id.fragmentContainer);
    }

    private void Fragment(final Fragment fragment) {
        FragmentTransaction transaction = ((AppCompatActivity) context).getSupportFragmentManager()
                .beginTransaction();
        try {
            transaction.add(R.id.fragmentContainer, fragment)
                    .commit();
        } catch (Exception e) {
            Toast.makeText(context, "ViewHolder " + e.getMessage(), Toast.LENGTH_SHORT).show();
        }
    }
}

I faced the same problem and resolved it by using the addOnChildAttachStateChangeListener RecyclerView callback on my onBindViewHolder callback :

listView.addOnChildAttachStateChangeListener(new RecyclerView.OnChildAttachStateChangeListener() {
        @Override
        public void onChildViewAttachedToWindow(@NotNull View view) {
            //Create your fragment container here using the view param and add your fragment to the container
        }

        @Override
        public void onChildViewDetachedFromWindow(@NotNull View view) {
            //Destroy the fragment container here

        }
    });

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM