Home Tutorials Training Consulting Books Company Contact us


Get more...

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?

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 a user interface, i.e., headless fragments.

Android devices exists in a variety of screen sizes and densities. Fragments simplify the reuse of components in different layouts and their logic. You can 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.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. 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. An activity may include multiple fragments for a certain configuration and for other configurations a different number. If a detailed fragment is there, the main activity tells the fragment that it should update itself. If a 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 {

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

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

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"
    xmlns:tools="http://schemas.android.com/tools"
    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"
        tools:layout="@layout/fragment_rsslist_overview">
    </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"
        tools:layout="@layout/fragment_rssitem_detail">
    </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="twoPaneMode">false</item>
</resources>

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

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

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

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

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

4.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.

4.6. 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.

4.7. Persisting data in fragments

4.7.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.

4.7.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.

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

5. Fragments for background processing

5.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.

5.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.

6. Displaying dialogs with DialogFragments

6.1. Using fragments for dialogs

A DialogFragment is a special fragment that displays a modal window, floating on top of the current activity window. An instance of DialogFragment can be used to display an existing Dialog implementation dialog or a custom view hierarchy.

The Dialog class is the base class for implementing a dialog. Android provides several standard dialog implementation, AlertDialog, ProgressDialog, DatePickerDialog or TimePickerDialog. Your DialogFragment can implement the onCreateDialog method and return such a dialog implementation.

If you want to create your custom view hierarchy you can build this in the onCreateView() method of the fragment.

6.2. Communication to the activity via the DialogFragment

Your implementation of DialogFragment communicates similar to other fragments to the activity.

One possible approach is that the fragment defines an interface which the activity must implement. The fragment can call back into the activity without knowing the implementation details of the activity.

An alternative approach is to use a shared ViewModel in which the fragment changes state which can trigger a method in the activity.

7. Exercise: Using fragments

The following tutorial demonstrates how to use fragments in a standard Android application. It does not use the support library. The application uses different numbers of fragments depending on portrait and landscape mode.

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

In landscape mode the main activity shows both fragments side by side.

7.1. Create Project

Create a new Android project with the following data.

Table 1. Android project
Property Value Watch-out

Application Name

RSS Reader

Company Domain

android.example.com

Package name

com.example.android.rssreader

Template

Empty Activity

Activity Name

RssfeedActivity

You need to change this. This is not the default name from the wizard.

Layout Name

activity_rssfeed

Backward compatibility

Not selected

Currently the wizard does select it by default, in this case you need to change it. Ensure that this option is not selected.

Verify that you activity extends Activity to ensure you are not using the backwards compatibility mode. If you extends another class, delete the project and create it again. Ensure that you deselect the "Backward Compatibility" flag in the project creation wizard.

7.2. Create layout files for the fragments

Create a new layout file called fragment_rssitem_detail.xml in the res/layout/ folder.

<?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>
The id must be correct, the fragment uses this to search for the TextView.

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/updateButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Press to update"/>

</LinearLayout>
Also here, the id must be correct.

7.3. Create fragment classes

Create the following 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 {

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

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

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.Fragment;
import android.content.Context;
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.updateButton);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                updateDetail("testing");
            }
        });
        return view;
    }

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

    @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() + uri);
        // send data to activity
        listener.onRssItemSelected(newTime);
    }
}

7.4. 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"
    xmlns:tools="http://schemas.android.com/tools"
    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"
        tools:layout="@layout/fragment_rsslist_overview">
    </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"
        tools:layout="@layout/fragment_rssitem_detail">
    </fragment>

</LinearLayout>
The tools: tag is optional. It points to a layout so that the preview in Android Studio can display the fragment.

7.5. RssfeedActivity

Change the RssfeedActivity class so that it can act as call back for the list fragment and update the detailed fragment.

package com.example.android.rssreader;

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

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 text) {
        DetailFragment fragment = (DetailFragment) getFragmentManager()
                .findFragmentById(R.id.detailFragment);
        fragment.setText(text);
    }

}

7.6. Verify that the application works as expected

Start your application. Both fragments should be displayed both in landscape and portrait mode. You can use the emulator controls to change the orientation of the emulator. If you press the button in the ListFragment, the DetailFragment gets updated.

rssfeedfirstversionresult

8. Exercise: Fragments depending on the configuration

The RssfeedActivity should show two fragments (two panes) if started in landscape mode. If it is started in portrait mode only one fragment should be shown. In this exercise you adjust your application to support this.

8.1. 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=".RssfeedActivity"
    android:orientation="vertical">

    <FrameLayout
        android:id="@+id/fragment_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal"/>

</LinearLayout>

8.2. Define a boolean flag dependent of the resource selector

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

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <bool name="twoPaneMode">true</bool>
</resources>

Create the same file for the landscape configuration with a different value. This file is located in your res/values-port folder.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <bool name="twoPaneMode">false</bool>
</resources>

8.3. Adjust the DetailFragment fragment

Adjust the DetailFragment class to the following.

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_TEXT ="text";

    @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 text = bundle.getString(EXTRA_TEXT);
            setText(text);
        }
    }

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

8.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.app.Fragment;
import android.app.FragmentTransaction;
import android.os.Bundle;

public class RssfeedActivity extends Activity implements
        MyListFragment.OnItemSelectedListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_rssfeed);
        if (getResources().getBoolean(R.bool.twoPaneMode)) {
            // all good, we use the fragments defined in the layout
            return;
        }
        // if savedInstanceState is null we do some cleanup
        if (savedInstanceState != null) {
            // cleanup any existing fragments in case we are in detailed mode (1)
            getFragmentManager().executePendingTransactions();
            Fragment fragmentById = getFragmentManager().
                    findFragmentById(R.id.fragment_container);
            if (fragmentById!=null) {
                getFragmentManager().beginTransaction()
                        .remove(fragmentById).commit();
            }
        }
        MyListFragment listFragment = new MyListFragment();
        getFragmentManager().beginTransaction()
                .replace(R.id.fragment_container, listFragment).commit();
    }

    @Override
    public void onRssItemSelected(String text) {
        if (getResources().getBoolean(R.bool.twoPaneMode)) {
            DetailFragment fragment = (DetailFragment) getFragmentManager()
                    .findFragmentById(R.id.detailFragment);
            fragment.setText(text);
        } 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_TEXT, text);
            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();
        }
    }
}
1 This cleanup is necessary because we are using dynamically switching between two fragments and one fragments. That is an unusual scenario, typically the selection of one or two fragment is based on the smallest width of a device and does not change at runtime. The FragmentManager caches the fragment to optimize performance, so we need to ensure that we remove an existing detailed fragment.

8.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. Exercise: Use a headless retained fragment to store the state

In this exercise you would like to remember the last selection of the user. We use a headless fragment for this.

9.1. Create headless fragment

Create a new class called SelectionStateFragment.

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;

public class SelectionStateFragment extends Fragment {
    public String lastSelection = "";

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return null;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);
    }
}

9.2. Use headless fragment to store the last selected value

Adjust the RssfeedActivity to the following.

package com.example.android.rssreader;

import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.os.Bundle;

public class RssfeedActivity extends Activity implements
        MyListFragment.OnItemSelectedListener {

    SelectionStateFragment stateFragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_rssfeed);
        stateFragment =
                (SelectionStateFragment) getFragmentManager()
                        .findFragmentByTag("headless");

        if(stateFragment == null) {
            stateFragment = new SelectionStateFragment();
            getFragmentManager().beginTransaction()
                    .add(stateFragment, "headless").commit();
        }

        if (getResources().getBoolean(R.bool.twoPaneMode)) {
            // restore state
            if (stateFragment.lastSelection.length()>0) {
                onRssItemSelected(stateFragment.lastSelection);
            }
            // otherwise all is good, we use the fragments defined in the layout
            return;
        }
        // if savedInstanceState is null we do some cleanup
        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();
        getFragmentManager().beginTransaction()
                .replace(R.id.fragment_container, listFragment).commit();
    }

    @Override
    public void onRssItemSelected(String text) {
        stateFragment.lastSelection = text;
        if (getResources().getBoolean(R.bool.twoPaneMode)) {
            DetailFragment fragment = (DetailFragment) getFragmentManager()
                    .findFragmentById(R.id.detailFragment);
            fragment.setText(text);
        } 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_TEXT, text);
            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.3. Validate your implementation

Test your application. Press the button in horizontal mode and verify that the detail activity shows a value. Switch the orientation of the emulator twice and validate that the activity still shows the same value.

Screenshot

10. Exercise: Use animations for the fragment transition

You can define which animation is used for the transition of the fragments. In this exercise you define your custom animations and use them for that.

10.1. Implement the animation

Create two animator resources in the res/animator folder of your app.

The first one should be called slide_down.xml and look like the following:

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:anim/accelerate_decelerate_interpolator"
    android:propertyName="translationY"
    android:valueType="floatType"
    android:valueFrom="0"
    android:valueTo="1280"
    android:duration="@android:integer/config_mediumAnimTime"/>

The second one should be called slide_up.xml and look like the following:

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:anim/accelerate_decelerate_interpolator"
    android:propertyName="translationY"
    android:valueType="floatType"
    android:valueFrom="1280"
    android:valueTo="0"
    android:duration="@android:integer/config_mediumAnimTime"/>

Enable these animations in before your replace operation of the fragment transaction.

FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.setCustomAnimations(R.animator.slide_up,R.animator.slide_down);
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);

// Commit the transaction
transaction.commit();

10.2. Verify

Start your application and verity that the fragment transition is using your animation.

11. Exercise: Using the DialogFragment class

The following demonstrates the usage of the DialogFragment fragment to display modal windows. Both the re-use of an dialog implementation and a custom layout for the dialog fragment is shown.

11.1. Create project and layout files

Create a new Android project with the com.android.vogella.dialogfragmentexample package.

Add at least two buttons to the activity layout. Both should point to the onClick method via their android:onClick property.

Create the following layout file, called fragment_username.xml.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/edit_name"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:orientation="vertical">

    <TextView
        android:id="@+id/lbl_your_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Enter user name" />

    <EditText
        android:id="@+id/username"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:imeOptions="actionDone"
        android:inputType="text" />
</LinearLayout>

11.2. Create fragments and adjust activity

Create the following two fragment classes.

package com.android.vogella.dialogfragmentexample;

import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.DialogInterface;
import android.os.Bundle;
import android.widget.Toast;

public class MyAlertDialogFragment extends DialogFragment {
 
@Override
 public Dialog onCreateDialog(Bundle savedInstanceState) {
  return new AlertDialog.Builder(getActivity())
    // set dialog icon
    .setIcon(android.R.drawable.stat_notify_error)
    // set Dialog Title
    .setTitle("Alert dialog fragment example")
      // Set Dialog Message
    .setMessage("This is a message")

    // positive button
    .setPositiveButton("OK", new DialogInterface.OnClickListener() {
     public void onClick(DialogInterface dialog, int which) {
      Toast.makeText(getActivity(), "Pressed OK", Toast.LENGTH_SHORT).show();
     }
    })
    // negative button
    .setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
     public void onClick(DialogInterface dialog, int which) {
      Toast.makeText(getActivity(), "Cancel", Toast.LENGTH_SHORT).show();
     }
    }).create();
 }
}
package com.android.vogella.dialogfragmentexample;

import android.app.DialogFragment;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.TextView;

public class MyDialogFragment extends DialogFragment implements TextView.OnEditorActionListener {

private EditText mEditText;


public interface UserNameListener {
  void onFinishUserDialog(String user);
}

 // Empty constructor required for DialogFragment
public MyDialogFragment() {}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
  View view = inflater.inflate(R.layout.fragment_username, container);
  mEditText = (EditText) view.findViewById(R.id.username);

  // set this instance as callback for editor action
  mEditText.setOnEditorActionListener(this);
  mEditText.requestFocus();
  getDialog().getWindow().setSoftInputMode(
    WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
  getDialog().setTitle("Please enter username");

  return view;
}

 @Override
 public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
  // Return input text to activity
  UserNameListener activity = (UserNameListener) getActivity();
  activity.onFinishUserDialog(mEditText.getText().toString());
  this.dismiss();
  return true;
 }
}

Change the code of your activity to the following listing.

package com.android.vogella.dialogfragmentexample;

import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

public class MainActivity extends Activity implements MyDialogFragment.UserNameListener {

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

    @Override
    public void onFinishUserDialog(String user) {
        Toast.makeText(this, "Hello, " + user, Toast.LENGTH_SHORT).show();
    }

    public void onClick(View view) {
        // close existing dialog fragments
        FragmentManager manager = getFragmentManager();
        Fragment frag = manager.findFragmentByTag("fragment_edit_name");
        if (frag != null) {
            manager.beginTransaction().remove(frag).commit();
        }
        switch (view.getId()) {
            case R.id.showCustomFragment:
                MyDialogFragment editNameDialog = new MyDialogFragment();
                editNameDialog.show(manager, "fragment_edit_name");
                break;
            case R.id.showAlertDialogFragment:
                MyAlertDialogFragment alertDialogFragment = new MyAlertDialogFragment();
                alertDialogFragment.show(manager, "fragment_edit_name");
                break;
        }
    }
}

11.3. Test dialog usage

If you run your application and click the corresponding button, your dialog should get displayed.