This tutorial describes how to use the Fragment class to create scalable and flexible Android applications.

1. Fragments

1.1. What are single-pane or multi-pane layouts?

Android devices exists in a variety of screen sizes and densities.

A panel or pane represents a part of the user interface. The term pane is a general term used to describe the concept that multiple views are combined into one compound view depending on the actual available space.

Panel

If not enough space is available only one panel is shown. This is typically called a single-pane layout.

Panel

If more space is available, multiple panels can be shown.

Panel

1.2. What are fragments?

A fragment is an independent Android component which can be used by an activity. A fragment encapsulates functionality so that it is easier to reuse within activities and layouts.

A fragment runs in the context of an activity, but has its own life cycle and typically its own user interface. It is also possible to define fragments without an user interface, i.e., headless fragments.

1.3. Fragments and Context access

Fragments don’t subclass the Context class. Therefore you have to use the getActivity() method to get the parent activity.

1.4. Advantages of using fragments

Fragments simplify the reuse of components in different layouts and their logic. You can easily build single-pane layouts for handsets (phones) and multi-pane layouts for tablets. You can also use fragments also to support different layout for landscape and portrait orientation on a smartphone.

As it is possible to dynamically add and remove fragments from an activity. The usage of fragments allows to design very flexible user interfaces.

The typical example is a list of items in an activity. On a tablet you see the details immediately on the same screen on the right hand side if you click on item. On a smartphone you jump to a new detail screen. This is depicted in the following graphic.

Fragments usage on a handheld device

The following discussion will assume that you have two fragments (main and detail), but you can also have more. We will also have one main activity and one detailed activity. On a tablet the main activity contains both fragments in its layout, on a handheld it only contains the main fragment.

The following screenshots demonstrates this usage.

Fragments usage on a tablet device

1.5. How to support different screensizes with fragments

It is possible to define in the layout file of an activity that it contains fragments (static definition). You can also modify the fragments of an activity at runtime (dynamic definition).

To display different fragments in your activities based on the actual available space you can:

  • Use one activity, which displays two fragments for tablets and on handset devices. In this case change at runtime the fragments displayed by the activity whenever necessary. In this scenario you typically define instances of the FrameLayout class as placeholder in your layout and add the fragments at runtime to them.

  • Use separate activities to host each fragment on a handset. For example, when the tablet UI uses two fragments in an activity, use the same activity for handsets, but supply an alternative layout that includes just one fragment. If the detailed fragment is there, the main activity tells the fragment that it should update itself. If the detail fragment is not available, the main activity starts the detailed activity.

Which option to select depends on the use case, typical the dynamic contribution is more flexible bit a bit harder to implement.

2. Defining and using fragments

2.1. Defining fragments

To define a new fragment you either extend the android.app.Fragment class or one of its subclasses. Subclasses are for example, ListFragment, DialogFragment, PreferenceFragment or WebViewFragment. The following code shows an example implementation.

package com.example.android.rssreader;

import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public class DetailFragment extends Fragment {
    public static final String EXTRA_URL ="url";

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_rssitem_detail,
                container, false);
        return view;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        Bundle bundle = getArguments();
        if (bundle != null) {
            String link = bundle.getString("url");
            setText(link);
        }
    }

        public void setText(String url) {
                TextView view = (TextView) getView().findViewById(R.id.detailsText);
                view.setText(url);
        }
}

2.2. Application communication with fragments

To increase reuse of fragments, they should not directly communicate with each other. Every communication of the fragments should be done via the host activity.

For this purpose a fragment should define an interface as an inner type. The fragment requires that the activity, which uses it, must implement this interface. This way you avoid that the fragment has any knowledge about the activity which uses it. In its onAttach() method it can check if the activity correctly implements this interface.

For example, assume you have a fragment which should communicate a value to its parent activity. This can be implemented like the following.

package com.example.android.rssreader;

import android.app.Activity;
import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;

public class MyListFragment extends Fragment {

        private OnItemSelectedListener listener;

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                        Bundle savedInstanceState) {
                View view = inflater.inflate(R.layout.fragment_rsslist_overview,
                                container, false);
                Button button = (Button) view.findViewById(R.id.button1);
                button.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                                updateDetail("fake");
                        }
                });
                return view;
        }

        public interface OnItemSelectedListener {
                public void onRssItemSelected(String link);
        }

        @Override
        public void onAttach(Context context) {
                super.onAttach(context);
                if (context instanceof OnItemSelectedListener) {
                        listener = (OnItemSelectedListener) context;
                } else {
                        throw new ClassCastException(context.toString()
                                        + " must implemenet MyListFragment.OnItemSelectedListener");
                }
        }

        @Override
        public void onDetach() {
                super.onDetach();
                listener = null;
        }

        // may also be triggered from the Activity
        public void updateDetail(String uri) {
                // create a string just for testing
                String newTime = String.valueOf(System.currentTimeMillis());

                // inform the Activity about the change based
                // interface defintion
                listener.onRssItemSelected(newTime);
        }
}

2.3. Passing parameters to fragments

An activity can pass a bundle to the fragment.

detailFragment = new DetailFragment();

// configure link
Bundle bundle = new Bundle();
bundle.putString("link", link);
detailFragment.setArguments(bundle);

The fragment gets this in its onActivityCreated method.

@Override
public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        Bundle bundle = getArguments();
        if (bundle != null) {
                setText(bundle.getString("link"));
        }
}

3. Fragment life-cycle

A fragment has its own life cycle. But it is always connected to the life cycle of the activity which uses the fragment.

Fragment lifecycle

If an activity stops, its fragments are also stopped. If an activity is destroyed, its fragments are also destroyed.

Table Title

Method Description

onAttach()

The fragment instance is associated with an activity instance.The fragment and the activity is not fully initialized. Typically you get in this method a reference to the activity which uses the fragment for further initialization work.

onCreate()

Fragment is created. The onCreate() method is called after the onCreate() method of the activity but before the onCreateView() method of the fragment.

onCreateView()

The fragment instance creates its view hierarchy. In the onCreateView() method the fragment creates its user interface. Here you can inflate a layout via the inflate() method call of the Inflator object passed as a parameter to this method.

In this method you should not interactive with the activity, the activity is not yet fully initialized.

There is no need to implement this method for headless fragments .The inflated views become part of the view hierarchy of its containing activity.

onActivityCreated()

The onActivityCreated() is called after the onCreateView() method when the host activity is created.

Activity and fragment instance have been created as well as the view hierarchy of the activity. At this point, view can be accessed with the findViewById() method. example.

In this method you can instantiate objects which require a Context object.

onStart()

The onStart() method is called once the fragment gets visible.

onResume()

Fragment becomes active.

onPause()

Fragment is visible but becomes not active anymore, e.g., if another activity is animating on top of the activity which contains the fragment.

onStop()

Fragment becomes not visible.

onDestroyView()

Destroys the view of the fragment. If the fragment is recreated from the backstack this method is called and afterwards the onCreateView method.

onDestroy()

Not guaranteed to be called by the Android platform.

4. Defining fragments for your activity

4.1. Adding fragments statically to the layout file

To use your new fragment, you can statically add it to an XML layout. In this case the android:name attribute points to the corresponding class as demonstrated by the following code snippet.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:baselineAligned="false"
    android:orientation="horizontal" >

    <fragment
        android:id="@+id/listFragment"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="match_parent"
        class="com.example.android.rssreader.MyListFragment" ></fragment>
    <fragment
        android:id="@+id/detailFragment"
        android:layout_width="0dp"
        android:layout_weight="2"
        android:layout_height="match_parent"
        class="com.example.android.rssreader.DetailFragment" >
    </fragment>

</LinearLayout>

Using this scenario makes sense in case you have different static layout files for different device configurations.

4.2. Handling dynamics in fragments

The FragmentManager class allows you to add, remove and replace fragments in the layout of your activity. It can accessed in an activity via the getFragmentManager() method.

The modifications must be performed in a transaction via the FragmentTransaction class.

To modify the fragments in an activity you typically define a FrameLayout placeholder in the layout file.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal" >

    <FrameLayout
        android:id="@+id/listcontainer"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <FrameLayout
        android:id="@+id/detailscontainer"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="gone" />

</LinearLayout>

You use the FragmentManager to replace the container with a fragment.

// get fragment manager
FragmentManager fm = getFragmentManager();

// add
FragmentTransaction ft = fm.beginTransaction();
ft.add(R.id.your_placehodler, new YourFragment());
// alternatively add it with a tag
// trx.add(R.id.your_placehodler, new YourFragment(), "detail");
ft.commit();

// replace
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.your_placehodler, new YourFragment());
ft.commit();

// remove
Fragment fragment = fm.findFragmentById(R.id.your_placehodler);
FragmentTransaction ft = fm.beginTransaction();
ft.remove(fragment);
ft.commit();

A new fragment replaces an existing fragment in this container.

If you want to add the transaction to the backstack of Android, you use the addToBackStack() method. This adds the action to the history stack of the activity and allows the user to revert this change via the back button.

4.3. Check if a fragment is present in the layout

To check if a fragment is part of your layout you can use the FragmentManager class. The isInLayout() method works on if the fragment as added to the activity via its layout.

DetailFragment fragment = (DetailFragment) getFragmentManager().
        findFragmentById(R.id.detail_frag);
if (fragment==null || ! fragment.isInLayout()) {
        // start new Activity
        }
else {
        fragment.update(...);
}

4.4. Determine how many fragments are present

As the logic in the activity depends on the scenario (single/multi pane), you typically write a check to determine in which mode you are. There are several approaches to perform this. One way is to define a configuration file in the values resource folder. The key / value pair defaults to false and a additional configuration file set this value to true for the desired screensize.

For example this is a possible default config.xml configuration file.

<resources>
    <item type="bool" name="dual_pane">false</item>
</resources>

In the values-land folder you would place a config.xml configuration file with a different value.

<resources>
    <item type="bool" name="dual_pane">true</item>
</resources>

In your code you can access the state via the following snippet.

getResources().getBoolean(R.bool.dual_pane);

5. Adding fragments transition to the backstack

You can add a FragmentTransition to the backstack to allow the user to use the back button to reverse the transition.

For this you can use the addToBackStack() method on the FragmentTransition object. == Animations for fragment transitions

During a fragment transaction, you can define animations based on the Property Animation API via the setCustomAnimations() method.

You can also use several standard Android animations via the setTransition() method call. These are defined via the constants starting with FragmentTransaction.TRANSIT_FRAGMENT_*.

Both methods allow you to define an entry animation and an existing animation.

6. Persisting data in fragments

6.1. Persisting data between application restarts

Frequently in your Android application you need to persist your application data. For this, you can for example use an SQLite database or a file.

6.2. Persisting data between configurations changes

If you want to persist data between configuration changes, you can also use the application object.

In addition to that you can use the setRetainState(true) method call on the fragment. This retains the fragment instances between configuration changes. It only works if the fragments are not added to the backstack. In this case the data must be stored as member (field).

Using this method is not recommended by Google for fragments which have a user interface. See [headlessfragments1].

You can use the onSaveInstanceState() method to place the data in the Bundle. You can retrieve that data the onActivityCreated() method.

7. Fragments for background processing

7.1. Headless Fragments

Fragments can be used without defining a user interface. These are called headless fragments. To implement such a fragment, return null in the onCreateView() method of your fragment.

It is recommended to use services for background processing. If you want to do this via your fragments, the second best solution is to use headless fragments in combination with the setRetainInstance() method. This way you don’t have to handle the configuration changes during your asynchronous processing yourself.

7.2. Retained headless fragments to handle configuration changes

Headless fragment are typically used to encapsulate some state across configuration changes or for a background processing task. For this purpose you would set your headless fragment to be retained. A retained fragment is not destroyed during configuration changes.

Retained headless fragment

To set your fragment to retained, call its setRetainInstance() method.

To add such a fragment to an activity you use the add() method of the FragmentManager class. If you need to refer to this Fragment later, you need to add it with a tag. This allows you to search for it via the findFragmentByTag() method of the FragmentManager.

The usage of the onRetainNonConfigurationInstance() in the activity is deprecated and should be replaced by retained headless fragments.

8. Fragments Tutorial

8.1. Target of this exercise

The following tutorial demonstrates how to use fragments in a standard Android application without using the support library. The application uses layouts with different fragments depending on portrait and landscape mode.

In portrait mode the RssfeedActivity shows one fragment. From this fragment the user can navigate to another activity which contains another fragment.

In landscape mode RssfeedActivity shows both fragments side by side.

8.2. Create Project

Create a new Android project with the following data. Do NOT use the compability layer.

Table Android project

Property Value

Application Name

RSS Reader

Company Domain

android.example.com

Package name

com.example.android.rssreader

Template

Empty Activity

Activity

RssfeedActivity

Layout

activity_rssfeed

8.3. Create standard layouts

Create the new layout files in the res/layout/ folder.

Create a new layout file called fragment_rssitem_detail.xml.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/detailsText"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="center_horizontal|center_vertical"
        android:layout_marginTop="20dip"
        android:text="Default Text"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:textSize="30dip" />

</LinearLayout>

Create a new layout file called fragment_rsslist_overview.xml.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Press to update"
         />

</LinearLayout>

8.4. Create fragment classes

Create the follows classes which are used as fragments. Start with the DetailFragment class.

package com.example.android.rssreader;

import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public class DetailFragment extends Fragment {
    public static final String EXTRA_URL ="url";

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_rssitem_detail,
                container, false);
        return view;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        Bundle bundle = getArguments();
        if (bundle != null) {
            String link = bundle.getString("url");
            setText(link);
        }
    }

        public void setText(String url) {
                TextView view = (TextView) getView().findViewById(R.id.detailsText);
                view.setText(url);
        }
}

Create the MyListFragment class. Despite its name it will not display a list of items. Instead it will just have a button which allow to send the current time to the details fragment.

package com.example.android.rssreader;

import android.app.Activity;
import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;

public class MyListFragment extends Fragment {

        private OnItemSelectedListener listener;
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                        Bundle savedInstanceState) {
                View view = inflater.inflate(R.layout.fragment_rsslist_overview,
                                container, false);
                Button button = (Button) view.findViewById(R.id.button1);
                button.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                                updateDetail("fake");
                        }
                });
                return view;
        }

        public interface OnItemSelectedListener {
            public void onRssItemSelected(String link);
          }

        @Override
          public void onAttach(Context context) {
            super.onAttach(context);
            if (context instanceof OnItemSelectedListener) {
              listener = (OnItemSelectedListener) context;
            } else {
              throw new ClassCastException(context.toString()
                  + " must implement MyListFragment.OnItemSelectedListener");
            }
          }

        // triggers update of the details fragment
        public void updateDetail(String uri) {
                // create fake data
                String newTime = String.valueOf(System.currentTimeMillis());
                // send data to activity
                listener.onRssItemSelected(newTime);
        }
}

8.5. Change the main layout file

Change the existing activity_rssfeed.xml file.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:baselineAligned="false"
    android:orientation="horizontal" >

    <fragment
        android:id="@+id/listFragment"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="match_parent"
        class="com.example.android.rssreader.MyListFragment" ></fragment>
    <fragment
        android:id="@+id/detailFragment"
        android:layout_width="0dp"
        android:layout_weight="2"
        android:layout_height="match_parent"
        class="com.example.android.rssreader.DetailFragment" >
    </fragment>

</LinearLayout>

8.6. RssfeedActivity

Change the RssfeedActivity class to the following code.

package com.example.android.rssreader;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;

public class RssfeedActivity extends Activity implements MyListFragment.OnItemSelectedListener{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_rssfeed);
    }

    @Override
    public void onRssItemSelected(String link) {
        DetailFragment fragment = (DetailFragment) getFragmentManager()
                .findFragmentById(R.id.detailFragment);
        fragment.setText(link);
    }

}

8.7. Validating

Start your application. Both fragments should be displayed both in landscape and portrait mode. If you press the button in the ListFragment, the DetailFragment gets updated.

rssfeedfirstversionresult

9. Exercise: Use different number of fragments depending on the configuration

9.1. Target of this exercise

The RssfeedActivity should show two fragments (two panes) if started in landscape mode. In this exercise you adjust your application to support this.

9.2. Create activity layout for portrait mode

Create the following activity_rssfeed.xml layout file based on the port resource qualifier for orientation.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <FrameLayout
        android:id="@+id/fragment_container"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:orientation="horizontal"
        android:layout_weight="1"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        >

    </FrameLayout>

</LinearLayout>

9.3. Define a boolean flag dependent of the resource selector

Create a file in your res/values folder called config.xml with the following setting.

<resources>
    <item type="bool" name="dual_pane">false</item>
</resources>

Create the same file in your res/values-land folder with a different value.

<resources>
    <item type="bool" name="dual_pane">true</item>
</resources>

9.4. Adjust the RssfeedActivity activity

Adjust the RssfeedActivity class to replace the existing fragment in case you are in single pane mode.

package com.example.android.rssreader;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;

public class RssfeedActivity extends Activity implements
        MyListFragment.OnItemSelectedListener {

           @Override
            protected void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.activity_main);
                if (findViewById(R.id.fragment_container) == null) {
                    // all good, we use the fragments defined in the layout
                    return;
                }
                // if savedInstanceState is null we need to load at least one fragment
                if (savedInstanceState != null) {
                    // cleanup any existing fragments in case we are in detailed mode
                    getFragmentManager().executePendingTransactions();
                    Fragment fragmentById = getFragmentManager().
                                    findFragmentById(R.id.fragment_container);
                    if (fragmentById!=null) {
                        getFragmentManager().beginTransaction()
                                .remove(fragmentById).commit();
                    }
                }
                MyListFragment listFragment = new MyListFragment();
                FrameLayout viewById = (FrameLayout) findViewById(R.id.fragment_container);
                getFragmentManager().beginTransaction()
                        .replace(R.id.fragment_container, listFragment).commit();
            }

    @Override
    public void onRssItemSelected(String link) {
        if (getResources().getBoolean(R.bool.twoPaneMode)) {
            DetailFragment fragment = (DetailFragment) getFragmentManager()
                    .findFragmentById(R.id.detailFragment);
            fragment.setText(link);
        } else {
            // replace the fragment
            // Create fragment and give it an argument for the selected article
            DetailFragment newFragment = new DetailFragment();
            Bundle args = new Bundle();
            args.putString(DetailFragment.EXTRA_URL, link);
            newFragment.setArguments(args);
            FragmentTransaction transaction = getFragmentManager().beginTransaction();

            // Replace whatever is in the fragment_container view with this fragment,
            // and add the transaction to the back stack so the user can navigate back

            transaction.replace(R.id.fragment_container, newFragment);
            transaction.addToBackStack(null);

            // Commit the transaction
            transaction.commit();

        }
    }


}

9.5. Validate your implementation

Test your application. If you run the application in portrait mode, you should see only one fragment. In horizontal mode you see both fragments.

Switch the orientation of the emulator. Press the button in portrait as well as in horizontal mode and verify that the detail activity shows the current time.

Screenshot

9.6. Android Fragment resources

Not yet listed.

Copyright © 2012-2016 vogella GmbH. Free use of the software examples is granted under the terms of the EPL License. This tutorial is published under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Germany license.

See Licence.