Best practice for nested fragments in Android 4.0, 4.1 (<4.2) without using the support library Best practice for nested fragments in Android 4.0, 4.1 (<4.2) without using the support library android android

Best practice for nested fragments in Android 4.0, 4.1 (<4.2) without using the support library


Limitations

So nesting fragments inside another fragment is not possible with xml regardless of which version of FragmentManager you use.

So you have to add fragments via code, this might seem like a problem, but in the long run makes your layouts superflexible.

So nesting without using getChildFragmentManger? The essence behind childFragmentManager is that it defers loading until the previous fragment transaction has finished. And of course it was only naturally supported in 4.2 or the support library.

Nesting without ChildManager - Solution

Solution, Sure! I have been doing this for a long time now, (since the ViewPager was announced).

See below; This is a Fragment that defers loading, so Fragments can be loaded inside of it.

Its pretty simple, the Handler is a really really handy class, effectively the handler waits for a space to execute on the main thread after the current fragment transaction has finished committing (as fragments interfere with the UI they run on the main thread).

// Remember this is an example, you will need to modify to work with your codeprivate final Handler handler = new Handler();private Runnable runPager;@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)    return inflater.inflate(R.layout.frag_layout, container, false);}@Overridepublic void onActivityCreated(Bundle savedInstanceState){    super.onActivityCreated(savedInstanceState);    runPager = new Runnable() {        @Override        public void run()        {          getFragmentManager().beginTransaction().addFragment(R.id.frag_container, MyFragment.newInstance()).commit();        }    };    handler.post(runPager);}/** * @see android.support.v4.app.Fragment#onPause() */@Overridepublic void onPause(){    super.onPause();    handler.removeCallbacks(runPager);}

I wouldn't consider it 'best practice', but I have live apps using this hack and I am yet to have any issues with it.

I also use this method for embedding view pagers - https://gist.github.com/chrisjenx/3405429


The best way to do this in pre-API 17 is to not do it at all. Trying to implement this behavior is going to cause issues. However that is not to say that it cannot be faked convincingly using the current API 14. What I did was the following:

1 - look at communication between fragments http://developer.android.com/training/basics/fragments/communicating.html

2 - move your layout xml FrameLayout from your existing Fragment to the Activity layout and hide it by giving a height of 0:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"          xmlns:tools="http://schemas.android.com/tools"          android:layout_width="fill_parent"          android:layout_height="fill_parent"><FrameLayout android:id="@+id/content"          android:layout_width="300dp"          android:layout_height="match_parent" /><FrameLayout android:id="@+id/lstResults"             android:layout_width="300dp"             android:layout_height="0dp"             android:layout_below="@+id/content"             tools:layout="@layout/treeview_list_content"/><FrameLayout android:id="@+id/anomalies_fragment"             android:layout_width="match_parent"             android:layout_height="match_parent"        android:layout_toRightOf="@+id/content" />

3 - Implement the interface in the parent Fragment

    OnListener mCallback;// Container Activity must implement this interfacepublic interface OnListener {    public void onDoSomethingToInitChildFrame(/*parameters*/);    public void showResults();    public void hideResults();}@Overridepublic void onAttach(Activity activity) {    super.onAttach(activity);    // This makes sure that the container activity has implemented    // the callback interface. If not, it throws an exception    try {        mCallback = (OnFilterAppliedListener) activity;    } catch (ClassCastException e) {        throw new ClassCastException(activity.toString()                + " must implement OnListener");    }}@Overridepublic void onActivityCreated(Bundle savedInstanceState) {    super.onActivityCreated(savedInstanceState);    mCallback.showResults();}@Overridepublic void onPause(){    super.onPause();    mCallback.hideResults();}public void onClickButton(View view){    // do click action here    mCallback.onDoSomethingToInitChildFrame(/*parameters*/);}

4 - Implement the interface in the parent Activity

public class YourActivity extends Activity implements yourParentFragment.OnListener {

public void onDoSomethingToInitChildFrame(/*parameters*/){    FragmentTransaction ft = getFragmentManager().beginTransaction();    Fragment childFragment = getFragmentManager().findFragmentByTag("Results");    if(childFragment == null)    {        childFragment = new yourChildFragment(/*parameters*/);        ft.add(R.id.lstResults, childFragment, "Results");    }    else    {        ft.detach(childFragment);        ((yourChildFragment)childFragment).ResetContent(/*parameters*/);        ft.attach(childFragment);    }    ft.commit();    showResultsPane();}public void showResults(){    FragmentTransaction ft = getFragmentManager().beginTransaction();    Fragment childFragment = getFragmentManager().findFragmentByTag("Results");    if(childFragment != null)        ft.attach(childFragment);    ft.commit();    showResultsPane();}public void showResultsPane(){    //resize the elements to show the results pane    findViewById(R.id.content).getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;    findViewById(R.id.lstResults).getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;}public void hideResults(){    //resize the elements to hide the results pane    findViewById(R.id.content).getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT;    findViewById(R.id.lstResults).getLayoutParams().height = 0;    FragmentTransaction ft = getFragmentManager().beginTransaction();    Fragment childFragment = getFragmentManager().findFragmentByTag("Results");    if(childFragment != null)        ft.detach(childFragment);    ft.commit();}

}

5 - Enjoy, with this method you get the same fluid functionality as with the getChildFragmentManager() function in a pre-API 17 envoronment. As you may have noticed the child fragment is no longer really a child of the parent fragment but now a child of the activity, this really cannot be avoided.


I had to deal with this exact issue due to a combination of NavigationDrawer, TabHost, and ViewPager which had complications with usage of the support library because of TabHost. And then I also had to support min API of JellyBean 4.1, so using nested fragments with getChildFragmentManager was not an option.

So my problem can be distilled to...

TabHost (for top level)+ ViewPager (for just one of the top level tabbed fragments)= need for Nested Fragments (which JellyBean 4.1 won't support)

My solution was to create the illusion of nested fragments without actually nesting fragments. I did this by having the main activity use TabHost AND ViewPager to manage two sibling Views whose visibility is managed by toggling layout_weight between 0 and 1.

//Hide the fragment used by TabHost by setting height and weight to 0LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 0);mTabHostedView.setLayoutParams(lp);//Show the fragment used by ViewPager by setting height to 0 but weight to 1lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1);mPagedView.setLayoutParams(lp);

This effectively allowed my fake "Nested Fragment" to operate as an independent view as long as I manually managed the relevant layout weights.

Here's my activity_main.xml:

<android.support.v4.widget.DrawerLayout    xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:id="@+id/drawer_layout"    android:layout_width="match_parent"    android:layout_height="match_parent"    tools:context="com.ringofblades.stackoverflow.app.MainActivity">    <TabHost        android:id="@android:id/tabhost"        android:layout_width="match_parent"        android:layout_height="match_parent">        <LinearLayout android:orientation="vertical"            android:layout_width="match_parent"            android:layout_height="match_parent">            <FrameLayout android:id="@android:id/tabcontent"                android:background="@drawable/background_image"                android:layout_width="match_parent"                android:layout_weight="0.5"                android:layout_height="0dp"/>            <android.support.v4.view.ViewPager                xmlns:tools="http://schemas.android.com/tools"                android:id="@+id/pager"                android:background="@drawable/background_image"                android:layout_width="match_parent"                android:layout_weight="0.5"                android:layout_height="0dp"                tools:context="com.ringofblades.stackoverflow.app.MainActivity">                <FrameLayout                    android:id="@+id/container"                    android:layout_width="match_parent"                    android:layout_height="match_parent" />            </android.support.v4.view.ViewPager>            <TabWidget android:id="@android:id/tabs"                android:layout_width="match_parent"                android:layout_height="wrap_content" />        </LinearLayout>    </TabHost>    <fragment android:id="@+id/navigation_drawer"        android:layout_width="@dimen/navigation_drawer_width"        android:layout_height="match_parent"        android:layout_gravity="start"        android:name="com.ringofblades.stackoverflow.app.NavigationDrawerFragment"        tools:layout="@layout/fragment_navigation_drawer" /></android.support.v4.widget.DrawerLayout>

Note that "@+id/pager" and "@+id/container" are siblings with 'android:layout_weight="0.5"' and 'android:layout_height="0dp"'. This is so that I can see it in the previewer for any screen size. Their weights will be manipulated in code during runtime, anyway.