Introduction
A fragment is a self-contained, modular section of an application’s user interface and corresponding behavior that can be embedded within an activity. Fragments can be assembled to create an activity during the application design phase, and added to, or removed from an activity during application runtime to create a dynamically changing user interface
The primary advantage of Fragments is the ease with which you can create dynamic and flexible UI designs that can be adapted to suite a range of screen sizes — from small-screen smartphones to tablets. Each Fragment is an independent module that is tightly bound to the Activity into which it is placed. Fragments can be reused within multiple activities, as well as laid out in a variety of combinations to suit multipane tablet UIs and added to, removed from, and exchanged within a running Activity to help build dynamic UIs.
Following are important points about fragment:
Fragment can provide:
You create fragments by extending Fragment class and you can insert a fragment into your activity layout by declaring the fragment in the activity's layout file, as a <fragment/> element.
Following is a typical example of how two UI modules defined by fragments can be combined into one activity for a tablet design, but separated for a handset design.
The application can embed two fragments in Activity A, when running on a tablet-sized device. However, on a handset-sized screen, there's not enough room for both fragments, so Activity A includes only the fragment for the list of articles, and when the user selects an article, it starts Activity B, which includes the second fragment to read the article.
There are three main types of Fragments:
Android applications use one of the following classes for Fragments:
android.app.Fragment
. The base class for all fragment definitions.android.app.FragmentManager
. The class for interacting with fragment objects inside an activity.android.app.FragmentTransaction
. The class for performing an atomic set of fragment operations.You can create a Fragment
programmatically or you can define a Fragment
in an XML layout file (which is also true of other UI components). If a Fragment
component is defined in an XML layout file, the android:name
attribute specifies the corresponding class. In the former case, you define a Fragment
by extending one of the following classes that are in the package android.app
:
Unlike Activities, Fragments don’t need to be registered in your manifest. This is because Fragments can exist only when embedded into an Activity, with their lifecycles dependent on that of the Activity to which they’ve been added.
Fragments are stored in the form of XML layout files and may be added to an activity either by placing appropriate <fragment>
elements in the activity’s layout file, or directly through code within the activity’s class implementation.
Before starting to use Fragments in an Android application, it is important to be aware that Fragments were not introduced to Android until version 3.0 of the Android SDK. An application that uses Fragments must, therefore, make use of the android-support-v4
Android Support Library in order to be compatible with older Android versions.
Fragment Life Cycle
Android fragments have their own life cycle very similar to an android activity.
Phase I: When a fragment gets created, it goes through the following states:
onAttach()
: when the fragment attaches to its host activity.onCreate()
: when a new fragment instance initializes, which always happens after it attaches to the host — fragments are a bit like viruses.onCreateView()
: when a fragment creates its portion of the view hierarchy, which is added to its activity’s view hierarchy.onActivityCreated()
: when the fragment’s activity has finished its own onCreate
event.Phase II: When the fragment becomes visible, it goes through these states:
onStart()
: when the fragment is visible; a fragment starts only after its activity starts and often starts immediately after its activity does.onResume()
: when the fragment is visible and interactable; a fragment resumes only after its activity resumes and often resumes immediately after the activity does.Phase III: When the fragment goes into the background mode, it goes through these states:
onPaused()
: when the fragment is no longer interactable; this occurs when either the fragment is about to be removed or replaced or when the fragment’s activity pauses.onStop()
: when the fragment is no longer visible; this occurs either after the fragment is about to be removed or replaced or when the fragment’s activity stops.Phase IV: When the fragment is destroyed, it goes through the following states:
onPaused()
onStop()
onDestroyView()
: when the view and related resources created in onCreateView are removed from the activity’s view hierarchy and destroyedonDestroy()
: when the fragment does its final clean up.onDetach()
: when the fragment is detached from its activity.When to use Fragments
Fragments make it easy to re-use components in different layouts, e.g. you can build single-pane layouts for handsets (phones) and multi-pane layouts for tablets. This is not limited to tablets; for example you can use Fragments also to support different layout for landscape and portrait orientation. But as tablets offer significantly more space you typically include more views into the layout and Fragments makes that easier.
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 handset you jump to a new detail screen. 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.
To check for an fragment you can use the FragmentManager
.
DetailFragment fragmentDetail = (DetailFragment) getFragmentManager(). findFragmentById(R.id.detail_fragment); if (fragment == null || !fragment.isInLayout()) { // start new Activity } else { fragment.update(...); }
Communication between Fragment and Activity
Once one or more fragments are embedded within an activity, the chances are good that some form of communication will need to take place both between the fragments and the activity and between one fragment and another. In fact, fragments should not communicate directly with each other. All communication should take place via the encapsulating activity.
In order for an activity to communicate with a fragment, the activity must identify the fragment object via the ID assigned to it using the findViewById()
method. Once this reference has been obtained, the activity can simply call the public methods of the fragment object.
Communicating in the other direction (from fragment to activity) is a little more complicated. In the first instance, the fragment must define a listener interface, which is then implemented within the activity class. For example, the following code declares an interface named ButtonClickListener
on a fragment class named DetailFragment
. The code also declares a variable in which a reference to the activity will later be stored:
public class DetailFragment extends Fragment { ButtonClickListener activityCallback; public interface ButtonClickListener { public void onButtonClick(int position, String text); } }
The above code dictates that any class that implements the ButtonClickListener
interface must also implement a callback method named onButtonClick
which, in turn, accepts an int
and a String
as arguments.
Next, the onAttach()
method of the fragment class needs to be overridden and implemented. This method is called automatically by the Android system when the fragment has been initialized and associated with an activity. The method is passed a reference to the activity in which the fragment is contained. The method must store a local reference to this activity and verify that it implements the ButtonClickListener
interface:
@Override public void onAttach(Activity activity) { super.onAttach(activity); try { activityCallback = (ButtonClickListener) activity; } catch (ClassCastException e) { throw new ClassCastException(activity.toString() + " must implement ButtonClickListener"); } }
Upon execution of this example, a reference to the activity will be stored in the local activityCallback
variable, and an exception will be thrown if that activity does not implement the ButtonClickListener
interface.
The next step is to call the callback method of the activity from within the fragment. When and how this happens is entirely dependent on the circumstances under which the activity needs to be contacted by the fragment. For the sake of an example, the following code calls the callback method on the activity when a button is clicked:
public void buttonClicked (View view) { activityCallback.onButtonClick(arg1, arg2); }
All that remains is to modify the activity class so that it implements the ButtonClickListener
interface. For example:
public class MainActivity extends FragmentActivity implements DetailFragment.ButtonClickListener { public void onButtonClick(String arg1, int arg2) { // implement code for callback method } }
As we can see from the above code, the activity declares that it implements the ButtonClickListener
interface of the DetailFragment
class and then proceeds to implement the onButtonClick()
method as required by the interface.
How to instantiate Fragment with arguments
There are some ways to instantiate and pass data to fragments in android development. The most recommended way of instantiate fragments with arguments is to have factory methods for this task.
public class PlanetDetailFragment extends Fragment { private String planet; private String PLANET_TITLE = "title"; private TextView tvPlanet; public static PlanetDetailFragment newInstance(String planet) { Bundle bundle = new Bundle(); bundle.putString(PLANET_TITLE, planet); PlanetDetailFragment fragment = new PlanetDetailFragment(); fragment.setArguments(bundle); return fragment; } private void readBundle(Bundle bundle) { if (bundle != null) { planet = bundle.getString(PLANET_TITLE); } } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_detail, container, false); tvPlanet = (TextView) view.findViewById(R.id.tvPlanet); readBundle(getArguments()); tvPlanet.setText(planet); return view; } }
In this example, to instantiate PlanetDetailFragment
you need to call;
PlanetDetailFragment fragment = PlanetDetailFragment.newInstance("Earth");
If android decides to recreate your activity and fragment, it is going to call no-argument constructor and the system guarantees to pass the arguments bundle to it.
Using DialogFragment
DialogFragment
is a specialized Fragment used when you want to display an overlay modal window within an activity that floats on top of the rest of the content.
This is typically used for displaying an alert dialog, a confirm dialog, or prompting the user for information within an overlay without having to switch to another Activity
.
DialogFragment
is now the canonical way to display overlays; using Dialog
directly is considered bad practice.
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.
The minimum that must be implemented when creating a DialogFragment
is either the onCreateView
method or the onCreateDialog
method. Use onCreateView
when the entire view of the dialog is going to be defined via custom XML. Use onCreateDialog
when you just need to construct and configure a standard Dialog
class (such as AlertDialog
) to display.
// file MyAlertDialogFragment.java public class MyAlertDialogFragment extends DialogFragment { @Override public Dialog onCreateDialog(Bundle savedInstanceState) { return new AlertDialog.Builder(getActivity()) .setIcon(android.R.drawable.ic_dialog_alert) .setTitle("Alert dialog fragment example") .setMessage("This is a message") .setPositiveButton("OK", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { Toast.makeText(getActivity(), "Pressed OK", Toast.LENGTH_SHORT).show(); } }) .setNegativeButton("Cancel", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { Toast.makeText(getActivity(), "Cancel", Toast.LENGTH_SHORT).show(); } }).create(); } } // file MainActivity.java public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); MyAlertDialogFragment dlg = new MyAlertDialogFragment(); dlg.show(getSupportFragmentManager(), "dlg_tag"); } }
You may also read about OnEditorActionListener
interface at Using DialogFragments.
How to pass value from DialogFragment to Activity
First, add OnValueChange
interface with onValueChange
method to DialogFragment
. Second, call onValueChange
method and pass the value.
// file MyAlertDialogFragment.java public class MyAlertDialogFragment extends DialogFragment { public interface OnValueChange { void onValueChange (int value); } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { int value = 1; return new AlertDialog.Builder(getActivity()) .setIcon(android.R.drawable.ic_dialog_alert) .setTitle("Alert dialog fragment example") .setMessage("This is a message") .setPositiveButton("OK", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { OnValueChange activity = (OnValueChange) getActivity(); activity.onValueChange(value); Toast.makeText(getActivity(), "Pressed OK", Toast.LENGTH_SHORT).show(); } }) .setNegativeButton("Cancel", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { Toast.makeText(getActivity(), "Cancel", Toast.LENGTH_SHORT).show(); } }).create(); } }
Third, implement OnValueChange
interface and override onValueChange
method.
// file MainActivity.java public class MainActivity extends AppCompatActivity implements MyAlertDialogFragment.OnValueChange { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); MyAlertDialogFragment dlg = new MyAlertDialogFragment(); dlg.show(getSupportFragmentManager(), "dlg_tag"); } @Override public void onValueChange(int value) { Toast.makeText(this, "Received " + value, Toast.LENGTH_SHORT).show(); } }
Example
There is a standard sequence of steps when adding a fragment to an activity using code. These steps are as follows:
beginTransaction()
method on the fragment manager instance. This returns a fragment transaction instance.add()
method of the fragment transaction instance, passing through as arguments the resource id of the view that is to contain the fragment and the fragment class instance.commit()
method of the fragment transaction.Each Activity includes a Fragment Manager to manage the Fragments it contains. You can access the Fragment Manager using the getFragmentManager method:
FragmentManager fragmentManager = getFragmentManager();
The Fragment Manager provides the methods used to access the Fragments currently added to the Activity, and to perform Fragment Transaction to add, remove, and replace Fragments.
The simplest way to add a Fragment to an Activity is by including it within the Activity’s layout using the fragment tag, as shown in following example.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent"> <fragment android:name="me.proft.planers.PlanetsListFragment" android:id="@+id/planets_list_fragment" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" /> <fragment android:name="me.proft.planets.PlanetsDetailFragment" android:id="@+id/planets_detail_fragment" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="3" /> </LinearLayout>
This technique works well when you use Fragments to define a set of static layouts based on various screen sizes. If you plan to dynamically modify your layouts by adding, removing, and replacing Fragments at run time, a better approach is to create layouts that use container Views into which Fragments can be placed at runtime, based on the current application state. Following example shows an XML snippet that you could use to support this latter approach.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent"> <FrameLayout android:id="@+id/planets_list_fragment" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" /> <FrameLayout android:id="@+id/planets_detail_fragment" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="3" /> </LinearLayout>
You then need to create and add the corresponding Fragments to their appropriate parent containers within the onCreate handler of your Activity using Fragment Transactions.
Fragment Transactions can be used to add, remove, and replace Fragments within an Activity at run time. Using Fragment Transactions, you can make your layouts dynamic — that is, they will adapt and change based on user interactions and application state.
A new Fragment Transaction is created using the beginTransaction method from the Activity's Fragment Manager. Modify the layout using the add, remove, and replace methods. When you are ready to execute the change, call commit to add the transaction to the UI queue.
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); // add fragment fragmentTransaction.add(R.id.planets_list_fragment, new PlanetsListFragment()); // remove fragment Fragment fragment = fragmentManager.findFragmentById(R.id.planet_detail_fragment); fragmentTransaction.remove(fragment); // replace fragment fragmentTransaction.replace(R.id.planet_detail_fragment, new PlanetDetailFragment(selected_index)); fragmentTransaction.commit();
Following shows the skeleton code used to populate an Activity’s layout with Fragments at run time.
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // inflate the layout containing the Fragment containers setContentView(R.layout.activity_main); FragmentManager fm = getFragmentManager(); // check to see if the Fragment back stack has been populated // if not, create and populate the layout. PlanetDetailFragment detailFragment = (PlanetDetailFragment)fm.findFragmentById( R.id.planet_detail_fragment); if (detailFragment == null) { FragmentTransaction ft = fm.beginTransaction(); ft.add(R.id.planet_detail_fragment, new PlanetDetailFragment()); ft.add(R.id.planets_list_fragment, new PlanetsListFragment()); ft.commit(); } }
The following example demonstrates how to use Fragments. The entry Activity (called MainActivity
) will use different layouts for portrait and for landscape mode.
In portrait mode MainActivity
will show one Fragment
with a list of names. If the user touches an item in the list, a second Activity called DetailActivity
will start and show the selected text.
In landscape mode MainActivity
will show two Fragments. The first is again the Fragments which shows the list of names. The second Fragment
shows the text of the current selected item. This is similar to the portrait mode, but the whole information will be shown on one screen.
Below is MainActivity.java.
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } }
The next step we have to do is creating our layout. As we said we need two different layout one for portrait and one for landscape. So we have to create two xml file under res/layout (for portrait mode) and one under res/layout-land (for landscape mode).
Layout for portrait mode
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout 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"> <fragment android:id="@+id/fragmentList" android:layout_width="wrap_content" android:layout_height="wrap_content" class="me.proft.sandbox.PlanetsListFragment" /> </RelativeLayout>
Layout for landscape mode
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent"> <fragment class="me.proft.sandbox.PlanetsListFragment" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="3" android:id="@+id/fragmentList" /> <fragment class="me.proft.sandbox.PlanetDetailFragment" android:id="@+id/fragment_detail" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" /> </LinearLayout>
Following is a fragment for list of planets.
public class PlanetsListFragment extends ListFragment { @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); String[] values = new String[] { "Mercury", "Venus", "Earth"}; ArrayAdapter adapter = new ArrayAdapter(getActivity(), android.R.layout.simple_list_item_1, values); setListAdapter(adapter); } @Override public void onListItemClick(ListView l, View v, int position, long id) { String item = (String) getListAdapter().getItem(position); PlanetDetailFragment fragment = (PlanetDetailFragment)getFragmentManager(). findFragmentById(R.id.fragment_detail); if (fragment != null && fragment.isInLayout()) { fragment.setText(item); } else { Intent intent = new Intent(getActivity().getApplicationContext(), PlanetDetailActivity.class); intent.putExtra(PlanetDetailFragment.ARG_ITEM_ID, item); startActivity(intent); } } }
Following is a fragment for detail information for selected planet.
public class PlanetDetailFragment extends Fragment { public static final String ARG_ITEM_ID = "item"; private String planet; TextView tvPlanet; // public static PlanetDetailFragment newInstance(String title) { // final Bundle args = new Bundle(); // args.putString(ARGUMENT_TITLE, title); // final PlanetDetailFragment fragment = new PlanetDetailFragment(); // fragment.setArguments(args); // return fragment; // } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // if (getArguments().containsKey(ARG_ITEM_ID)) { // planet = getArguments().getString(ARG_ITEM_ID); // } } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_detail, container, false); // if (point != null) { // tvPlanet = (TextView)view.findViewById(R.id.tvPlanet); // tvPlanet.setText(planet); // } return view; } public void setText(String item){ tvPlanet = (TextView)getView().findViewById(R.id.tvPlanet); tvPlanet.setText(item); } }
Below is layout for fragment with details
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/tvPlanet" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Select planet" android:textStyle="bold" android:textSize="16dip" /> </LinearLayout>
Following is activity for planet's details
public class PlanetDetailActivity extends FragmentActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { finish(); return; } setContentView(R.layout.fragment_detail); // if (savedInstanceState == null) { // Bundle arguments = new Bundle(); // String point = getIntent().getStringExtra(PointDetailFragment.ARG_ITEM_ID); // arguments.putString(PointDetailFragment.ARG_ITEM_ID, point); // // PlanetDetailFragment fragment = new PlanetDetailFragment(); // fragment.setArguments(arguments); // getSupportFragmentManager().beginTransaction(). // add(android.R.id.fragment_detail, fragment).commit(); // fragment.setText(s); // } Intent i = this.getIntent(); String s = i.getExtras().getString(PlanetDetailFragment.ARG_ITEM_ID); TextView tvPlanet = (TextView) findViewById(R.id.tvPlanet); tvPlanet.setText(s); } }
Fragment Transitions
You can add animation to fragment transitions. Fragment allows you to add animation for enter and exit transitions. To add enter transition, you need to call setEnterTransition()
method on the Fragment
object passing animation object to it. To add exist transition, call setExitTransition()
method on the Fragment
object.
You can create transition object by instantiating one of the transition classes such as Fade, Slide and Explode and setting required properties.
Fragment enter transition example
FragmentManager fm = getSupportFragmentManager(); FragmentTransaction ft = fm.beginTransaction(); Fragment f = new SampleFragment(); Slide slide = new Slide(); slide.setDuration(500); slide.setInterpolator(new DecelerateInterpolator()); f.setEnterTransition(slide); ft.replace(R.id.wrapper, f); ft.commit();
Fragment exit transition example
FragmentManager fm = getSupportFragmentManager(); FragmentTransaction ft = fm.beginTransaction(); Fragment f = new SampleFragment(); Explode e = new Explode(); e.setDuration(800); e.setMode(Explode.MODE_OUT); e.setPropagation(new CircularPropagation()); e.setInterpolator(new AccelerateInterpolator()); f.setExitTransition(e); ft.replace(R.id.wrapper, f); ft.addToBackStack(null); ft.commit();
Useful links