Multiple Fragments as Children in Android Apps
Searching for “adding fragments programmatically” will yield plenty of answers for replacing a fragment using transactions, but you won’t find a lot of guidance if you need to add fragments into a list of an unknown size. (I ran into this problem while implementing dialogs which might need to stack on top of another.) I thought I would share my solution for this problem along with some pitfalls that I ran into while attempting to engineer a solution.
Adding Multiple Fragments
In the activity layout XML, we can add a
View such as a
LinearLayout or a
FrameLayout to act as a container for our fragments. Give this view an
android:id so we can reference it later in the activity class.
<FrameLayout android:id="@+id/dialog_container" android:layout_width="match_parent" android:layout_height="match_parent"/>
Once the container is in the layout, we can now reference it using its
android:id. This will come in handy later once we start adding our fragment container views.
Adding the fragment container view
As you probably know, a view can have children. We can add a
FrameLayout as a child to this container for each fragment.
FrameLayout dialogFrame = new FrameLayout(this); mDialogContainer.addView(dialogFrame);
At this point, the tree looks something like this:
- Activity -- FrameLayout // @id/dialog_container --- FrameLayout // The dialogFrame we just added
But to use a
FragmentTransaction to add our fragment to this
FrameLayout, we need the ID of the layout.
FragmentManager manager = activity.getSupportFragmentManager(); FragmentTransaction transaction = manager.beginTransaction(); transaction .replace(containerId, fragment) .commit();
containerId in the
Container view ID
You can use
View.getId() to get the ID of any
View. But, by default, instantiating a new
View does not set its own ID and
getId() will return
View.NO_ID. We are going to have to set this ID ourselves, but there are some things to consider:
- View IDs need to be unique (at least per fragment/activity) - searching with
findViewById()will become ambiguous otherwise.
- View IDs set for views in layout files are dynamically generated at compile-time.
These two factors make it difficult to generate our own view IDs without some amount of integration with the resource library - we don’t any collisions with existing IDs. This is where
View.generateViewId() comes in. This static function will generate an ID that is guaranteed to not already be in the
R.id that was generated at compile-time.
FrameLayout dialogFrame = new FrameLayout(this); int containerId = View.generateViewId(); dialogFrame.setId(containerId); mDialogContainer.addView(dialogFrame);
From this point, we can use the
containerId to reference the
FrameLayout that we want the
FragmentTransaction to add our fragment to.
After a couple fragments are added this way, the tree will look like this:
- Activity -- FrameLayout // @id/dialog_container --- FrameLayout ---- Fragment --- FrameLayout ---- Fragment --- FrameLayout ---- Fragment --- ...
Something I ran into while implementing this solution was a rather cryptic exception thrown from the bowels of the Android lifecycle:
IllegalStateException: Restarter must be created only during owner’s initialization stage
It turns out that the fragment being added to the
FrameLayout must be a new instance for each new
FrameLayout, even if the old one no longer exists. At least, that was my solution to this exception.
Scoping IDs in your fragment layout
Another interesting bug that you might run into involves views in your fragment layout that share the same ID. If multiple instances of the same fragment are being added to your activity tree, that means all the views in your fragment layout that have an
android:id now share the same ID. This can be problematic if you use
findViewById() as opposed to a binding. If you do use
findViewById() in your fragment, make sure you call it from the scope of the fragment view as opposed to the parent activity, or you’ll run into some unintended behaviors. In my case, touch input was being passed to the first fragment added as opposed to the current fragment.