0

When you open my app, I start off by having a viewpager with various tabs at the top that you can click on that displays different fragments per tab.

Inside the fragment called "TEST" is a recyclerview with each item in the recyclerview being child fragments.

test

There are a few buttons that rest on top of each fragment in the recyclerview so that if you click on the map button, the fragment can change to another fragment that displays a map etc.

In my recyclerview adapter, I inflate the following xml (example.xml) which would represent each line of the recyclerview:

<?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/groupholder"
        android:background="#FFF">

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/fragment_container">
        </RelativeLayout>

    </RelativeLayout>

The fragment_container is to hold an inflated fragment.

The below is the relevant parts from the RecyclerView.Adapter class that is used to populate the recyclerview:

 //the view holder class
class ExampleViewHolder extends RecyclerView.ViewHolder {

    RelativeLayout fragmentContainer;

    public ExampleViewHolder(View view) {
        super(view);
        //we are creating a framecontainer for the fragment
        fragmentContainer = (RelativeLayout) view.findViewById(R.id.fragment_container);
        }   

}

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        //inflating the layout that contains the fragment for the ExampleViewHolder
        v = LayoutInflater.from(parent.getContext()).inflate(R.layout.example, parent, false);
    }


    @Override
    public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
    if (holder instanceOf ExampleViewHolder) {


        //The "f" below is the fragment that gets past into this Recycler.Adapter through the adapter's constructor.
         f.getChildFragmentManager().beginTransaction().add(((ExampleViewHolder) holder).fragmentContainer.getId(), new PlaceImageFragment()).commit();
        //this must execute immediately.
        //This is the part that crashes the app
        f.getChildFragmentManager().executePendingTransactions();
        }

    }

Since the commit command is scheduled to be executed asynchronously on the process's main thread (http://developer.android.com/reference/android/app/FragmentManager.html#executePendingTransactions()), I have to call the executePendingTransactions() to get the fragmentmanager to execute the command immediately.

If I do not call the executePendingTransactions(), the app works but the fragments are not populated immediately and I can also see that on screen as the fragments are populated really slowly or not at all in some cases.

However, if I do call executePendingTransactions(), the app crashes with the error saying that the line executePendingTransactions() was the reason why the app crash:

java.lang.IllegalArgumentException: No view found for id 0x7f0f0096 (com.example.simon:id/fragment_container) for fragment PlaceImageFragment{52e819f8 #0 id=0x7f0f0096}

Any ideas how I can get this to work?

EDIT:

I think that the reason why this is giving me a no view found is that the getChildFragmentManager is looking within the parent fragments layout xml for the id/fragment_container but it will not be in there as the parent fragment only contains the recyclerview in the xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_height="match_parent"
    android:id="@+id/main_recyclerview_holder">
<!--The padding here creates the space at the top of the ad-->
    <android.support.v7.widget.RecyclerView
        android:id="@+id/main_recyclerview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
/>

</RelativeLayout>
Simon
  • 19,658
  • 27
  • 149
  • 217
  • 1
    *...is that the getChildFragmentManager is looking within the parent fragments layout xml for the id/fragment_container...* - or at the moment when you're trying to do the transaction(in onBindViewHolder()) the row/item view built in onCreateViewHolder() isn't yet added to the RecyclerView parent(so it isn't present in the view hierarchy). Probably not what you want to hear but you should really avoid placing fragments in widgets that recycle stuff, especially as you really don't obtain any real benefits from their use. – user Sep 24 '15 at 16:47
  • But if you have let say 3 buttons and each button does something different, like one would show a map, another would show a picture, another would show text etc, would it not be beneficial? Currently, I'm switching between each view (the map, picture, text) by creating the views one on top of the other and then using view.bringToFront and setVisibility to bring the view to front and hid the other views when the user clicks the button. – Simon Sep 24 '15 at 16:50
  • *would it not be beneficial?* - no, it will also be wasteful as you'll have in each row(consuming memory and slowing down view layout/measuring) completely useless views. `RecyclerView.Adapter` has a special system to deal with this scenario by offering the user the method `getItemViewType()`(where you can tell the adapter that you have different types of rows which should be managed). – user Sep 24 '15 at 16:57
  • Agreed but there is a link between the map, picture and text. I cannot separate the link and display the three separately one under the other using getItemViewType() which is why i'm currently drawing all the views on top of each other and using view.bringToFront and setVisibility to switch between them - I think the way I'm currently doing it is more wasteful than using fragments as fragments are designed to swap between views quickly and the map fragment does not have to be loaded if the user does not click on the map button. – Simon Sep 24 '15 at 17:04
  • Use a custom view, e.g. RowView, which manages its internal dynamics - view flipping, listeners, maps and so on - and populate the recycler with that. This way you have access to the view lifecycle and can (eventually, if needed) inflate fragments (say a MapFragment) with no issues. – natario Sep 25 '15 at 12:46
  • does the creation of the newPlaceFragment happen in test fragment? what i mean is does it get created in test fragment – Elltz Oct 01 '15 at 03:52
  • Test is the main fragment linked to the viewpager. The newPlaceFragment is the fragment that sits within the recyclerview in the "Test" fragment – Simon Oct 01 '15 at 19:06

1 Answers1

0

Try to change your fragment in onBidViewHolder() and initiate it with recyclerViewAdapter.notifyItemChanged(position). And if you change your logic from fragments to getItemViewType() it would be better solution.

Edit:

Your problem started when you tryed to set fragment on dynamically created view, and when you call executePendingTransactions() there is still no view(View object is created but still not on screen) this problem can be fixed by:

((ExampleViewHolder) holder).fragmentContainer.post(new Runnable() {
            @Override
            public void run() {
                f.getChildFragmentManager().executePendingTransactions();
            }
        });

so this code will be called only when view will be created. But you still have problem with it's id because all items of recyclerView have identical id's, you can avoid this by:

int i = ((ExampleViewHolder) holder).fragmentContainer.generateViewId();
        ((ExampleViewHolder) holder).fragmentContainer.setId(i);

        f.getChildFragmentManager().beginTransaction().add(i, new PlaceImageFragment()).commit();

but this code can work only starting from API level 17. Or you can create your own static id's(bad solution).

And i still can't see problems with using getItemViewType() logic - you can create 3 different view type with copyes of your buttons on them, this buttons will be different objects but they will call same onClickListeners.

For more information about dynamycally created views and fragments look: How do I add a Fragment to an Activity with a programmatically created content view

Community
  • 1
  • 1
q21forfun
  • 36
  • 5
  • I cannot change the logic from fragment to getItemViewType as the picture, text and the map buttons are intrinsically linked to one another. Please can you read the commentary below the question for more details. – Simon Sep 30 '15 at 19:08