A fragment has a separate layout XML file. A Fragment encapsulates functionality so that it is easier to reuse within activities and layouts. Fragments are standalone components that can contain views, events and logic. Within a fragment-oriented architecture, applications become navigation containers that are primarily responsible for navigation to other activities, presenting fragments and passing data.
Embedding a Fragment in an Activity
There are two ways to add a fragment to an activity: dynamically using Java and statically using XML.
Before embedding a "support" fragment in an Activity make sure the Activity is changed to extend from FragmentActivity
orAppCompatActivity
which adds support for the fragment manager to all Android versions. Any activity using fragments should make sure to extend from FragmentActivity
or AppCompatActivity
:
import android.support.v7.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
// ...
}
Statically
To add the fragment statically, simply embed the fragment in the activity's xml 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="vertical" >
<fragment
android:name="com.example.android.FooFragment"
android:id="@+id/fooFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
Note: You will likely need to change the path for FooFragment based on your project setup.
Dynamically
The second way is by adding the fragment
dynamically in Java using the
FragmentManager
. The
FragmentManager
class and the
FragmentTransaction class allow you to add, remove and replace fragments in the layout of your activity at runtime.
In this case, you want to add a "placeholder" container (usually a FrameLayout
) to your activity where the fragment is inserted at runtime:
<?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" >
<FrameLayout
android:id="@+id/your_placeholder"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>
</LinearLayout>
// Begin the transaction
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
// Replace the contents of the container with the new fragment
ft.replace(R.id.your_placeholder, new FooFragment());
// or ft.add(R.id.your_placeholder, new FooFragment());
// Complete the changes added above
ft.commit();
If the fragment should always be within the activity, use XML to statically add the fragment but in more complex cases be sure to use the Java-based approach.
Fragment Lifecycle
onAttach()
is called when a fragment is connected to an activity.
onCreate()
is called to do initial creation of the fragment.
onCreateView()
is called by Android once the Fragment should inflate a view.
onViewCreated()
is called after onCreateView()
and ensures that the fragment's root view is non-null
. Any view setup should happen here. E.g., view lookups, attaching listeners.
onActivityCreated()
is called when host activity has completed its onCreate()
method.
onStart()
is called once the fragment is ready to be displayed on screen.
onResume()
- Allocate “expensive” resources such as registering for location, sensor updates, etc.
onPause()
- Release “expensive” resources. Commit any changes.
onDestroyView()
is called when fragment's view is being destroyed, but the fragment is still kept around.
onDestroy()
is called when fragment is no longer in use.
onDetach()
is called when fragment is no longer connected to the activity.
The lifecycle execution order is mapped out below:
The most common ones to override are onCreateView
which is in almost every fragment to setup the inflated view, onCreate
for any data initialization and onActivityCreated
used for setting up things that can only take place once the Activity has been fully created.
Here's an example of how you might use the various fragment lifecycle events:
public class SomeFragment extends Fragment {
ThingsAdapter adapter;
FragmentActivity listener;
// This event fires 1st, before creation of fragment or any views
// The onAttach method is called when the Fragment instance is associated with an Activity.
// This does not mean the Activity is fully initialized.
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
this.listener = (FragmentActivity) activity;
}
// This event fires 2nd, before views are created for the fragment
// The onCreate method is called when the Fragment instance is being created, or re-created.
// Use onCreate for any standard setup that does not require the activity to be fully created
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ArrayList<Thing> things = new ArrayList<Thing>();
adapter = new ThingsAdapter(getActivity(), things);
}
// The onCreateView method is called when Fragment should create its View object hierarchy,
// either dynamically or via XML layout inflation.
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_some, parent, false);
}
// This event is triggered soon after onCreateView().
// onViewCreated() is only called if the view returned from onCreateView() is non-null.
// Any view setup should occur here. E.g., view lookups and attaching view listeners.
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
ListView lv = (ListView) view.findViewById(R.id.lvSome);
lv.setAdapter(adapter);
}
// This method is called after the parent Activity's onCreate() method has completed.
// Accessing the view hierarchy of the parent activity must be done in the onActivityCreated.
// At this point, it is safe to search for activity View objects by their ID, for example.
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
}
Looking Up a Fragment Instance
Often we need to lookup or find a fragment instance within an activity layout file. There are a few methods for looking up an existing fragment instance:
- ID - Lookup a fragment by calling
findFragmentById
on the FragmentManager
- Tag - Lookup a fragment by calling
findFragmentByTag
on the FragmentManager
- Pager - Lookup a fragment by calling
getRegisteredFragment
on a PagerAdapter
Each method is outlined in more detail below.
Finding Fragment By ID
If the fragment was statically embedded in the XML within an activity and given an android:id
such as fragmentDemo
then we can lookup this fragment by id by calling findFragmentById
on the FragmentManager
:
public class MainActivity extends AppCompatActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
DemoFragment fragmentDemo = (DemoFragment)
getSupportFragmentManager().findFragmentById(R.id.fragmentDemo);
}
}
}
Finding Fragment By Tag
If the fragment was dynamically added at runtime within an activity then we can lookup this fragment by tag by callingfindFragmentByTag
on the FragmentManager
:
public class MainActivity extends AppCompatActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
// Let's first dynamically add a fragment into a frame container
getSupportFragmentManager().beginTransaction().
replace(R.id.flContainer, new DemoFragment(), "SOMETAG").
commit();
// Now later we can lookup the fragment by tag
DemoFragment fragmentDemo = (DemoFragment)
getSupportFragmentManager().findFragmentByTag("SOMETAG");
}
}
}
Finding Fragment Within Pager
If the fragment was dynamically added at runtime within an activity into a
ViewPager
using a
FragmentPagerAdapter then we can lookup the fragment by upgrading to a
SmartFragmentStatePagerAdapter
as
described in the ViewPager guide. Now with the adapter in place, we can also easily access any fragments within the ViewPager using
getRegisteredFragment
:
// returns first Fragment item within the pager
adapterViewPager.getRegisteredFragment(0);
Note that the ViewPager
loads the fragment instances lazily similar to the a ListView
recycling items as they appear on screen. If you attempt to access a fragment that is not on screen, the lookup will return null
.
Communicating with Fragments
Fragments should not directly communicate with each other, only through an activity. Fragments should be modular and reusable components. Let the activity respond to intents and fragment callbacks in most cases.
There are three ways a fragment and an activity can communicate:
- Bundle - Activity can construct a fragment and set arguments
- Methods - Activity can call methods on a fragment instance
- Listener - Fragment can fire listener events on an activity via an interface
Fragment with Arguments
In certain cases, your fragment may want to accept certain arguments. A common pattern is to create a static newInstance
method for creating a Fragment with arguments. This is because a Fragment must have only a constructor with no arguments. Instead, we want to use the setArguments
method such as:
public class DemoFragment extends Fragment {
// Creates a new fragment given an int and title
// DemoFragment.newInstance(5, "Hello");
public static DemoFragment newInstance(int someInt, String someTitle) {
DemoFragment fragmentDemo = new DemoFragment();
Bundle args = new Bundle();
args.putInt("someInt", someInt);
args.putString("someTitle", someTitle);
fragmentDemo.setArguments(args);
return fragmentDemo;
}
}
This sets certain arguments into the Fragment for later access within onCreate
. You can access the arguments later by using:
public class DemoFragment extends Fragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Get back arguments
int SomeInt = getArguments().getInt("someInt", 0);
String someTitle = getArguments().getString("someTitle", "");
}
}
Now we can load a fragment dynamically in an Activity with:
// Within the activity
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
DemoFragment fragmentDemo = DemoFragment.newInstance(5, "my title");
ft.replace(R.id.your_placeholder, fragmentDemo);
ft.commit();
This pattern makes passing arguments to fragments for initialization fairly straightforward.
Fragment Methods
If an activity needs to make a fragment perform an action after initialization, the easiest way is by having the activity invoke a method on the fragment instance. In the fragment, add a method:
public class DemoFragment extends Fragment {
public void doSomething(String param) {
// do something in fragment
}
}
and then in the activity, get access to the fragment using the fragment manager and call the method:
public class MainActivity extends AppCompatActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DemoFragment fragmentDemo = (DemoFragment)
getSupportFragmentManager().findFragmentById(R.id.fragmentDemo);
fragmentDemo.doSomething("some param");
}
}
and then the activity can communicate directly with the fragment by invoking this method.
Fragment Listener
If a fragment needs to communicate events to the activity, the fragment should
define an interface as an inner type and require that the activity must implement this interface:
import android.support.v4.app.Fragment;
public class MyListFragment extends Fragment {
// ...
// Define the listener of the interface type
// listener will the activity instance containing fragment
private OnItemSelectedListener listener;
// Define the events that the fragment will use to communicate
public interface OnItemSelectedListener {
// This can be any number of events to be sent to the activity
public void onRssItemSelected(String link);
}
// Store the listener (activity) that will have events fired once the fragment is attached
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (activity instanceof OnItemSelectedListener) {
listener = (OnItemSelectedListener) activity;
} else {
throw new ClassCastException(activity.toString()
+ " must implement MyListFragment.OnItemSelectedListener");
}
}
// Now we can fire the event when the user selects something in the fragment
public void onSomeClick(View v) {
listener.onRssItemSelected("some link");
}
}
and then in the activity we have to implement the OnItemSelectedListener
listener:
import android.support.v7.app.AppCompatActivity;
// Activity implements the fragment listener to handle events
public class RssfeedActivity extends AppCompatActivity implements MyListFragment.OnItemSelectedListener {
// Can be any fragment, `DetailFragment` is just an example
DetailFragment fragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_rssfeed);
// Get access to the detail view fragment by id
fragment = (DetailFragment) getSupportFragmentManager()
.findFragmentById(R.id.detailFragment);
}
// Now we can define the action to take in the activity when the fragment event fires
// This is implementing the `OnItemSelectedListener` interface methods
@Override
public void onRssItemSelected(String link) {
if (fragment != null && fragment.isInLayout()) {
fragment.setText(link);
}
}
}
in order to keep the fragment as re-usable as possible. For more details about this pattern, check out our detailed
Creating Custom Listeners guide.
Understanding the FragmentManager
The
FragmentManager is responsible for all runtime management of fragments including adding, removing, hiding, showing, or otherwise navigating between fragments. As shown above, the fragment manager is also responsible for finding fragments within an activity. Important available methods are outlined below:
Method | Description |
addOnBackStackChangedListener | Add a new listener for changes to the fragment back stack. |
beginTransaction() | Creates a new transaction to change fragments at runtime. |
findFragmentById(int id) | Finds a fragment by id usually inflated from activity XML layout. |
findFragmentByTag(String tag) | Finds a fragment by tag usually for a runtime added fragment. |
popBackStack() | Remove a fragment from the backstack. |
executePendingTransactions() | Forces committed transactions to be applied. |
ActionBar Menu Items and Fragments
One common case is the need for fragment-specific menu items that only show up for that fragment. This can be done by adding an onCreateOptionsMenu
method to the fragment directly. This works just like the one for the activity:
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.fragment_menu, menu);
}
You then also need to notify the fragment that it's menu items should be loaded within the fragment's onCreate
method:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
Clicks can be handled using onClick
property as usual or more typically in this case, using the onOptionsItemSelected
method in the fragment:
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// handle item selection
switch (item.getItemId()) {
case R.id.edit_item:
// do s.th.
return true;
default:
return super.onOptionsItemSelected(item);
}
}
Note that the fragment’s method is called only when the Activity didn’t consume the event first. Be sure to check out a more detailed guide about
fragments and action bar if you have more questions.
Navigating Between Fragments
There are several methods for navigating between different fragments within a single Activity. The primary options are:
- TabLayout - Tabs at the top
- Fragment Navigation Drawer - Slide out navigation menu
- ViewPager - Swiping between fragments
Check the guides linked above for detailed steps for each of these approaches.
Managing Fragment Backstack
A record of all Fragment transactions is kept for each Activity by the FragmentManager. When used properly, this allows the user to hit the device’s back button to remove previously added Fragments (not unlike how the back button removes an Activity). Simply call
addToBackstack on each FragmentTransaction that should be recorded:
// Create the transaction
FragmentTransaction fts = getSupportFragmentManager().beginTransaction();
// Replace the content of the container
fts.replace(R.id.flContainer, new FirstFragment());
// Append this transaction to the backstack
fts.addToBackStack("optional tag");
// Commit the changes
fts.commit();
Programmatically, you can also pop from the back stack at any time through the manager:
FragmentManager fragmentManager = getSupportFragmentManager();
if (fragmentManager.getBackStackEntryCount() > 0) {
fragmentManager.popBackStack();
}
With this approach, we can easily keep the history of which fragments have appeared dynamically on screen and allow the user to easily navigate to previous fragments.
Fragment Hiding vs Replace
In many of the examples above, we call transaction.replace(...)
to load a dynamic fragment which first removes the existing fragment from the activity invoking onStop
and onDestroy
for that fragment before adding the new fragment to the container. This can be good because this will release memory and make the UI snappier. However, in many cases, we may want to keep both fragments around in the container and simply toggle their visibility. This allows all fragments to maintain their state because they are never removed from the container. To do this, we might modify this code:
// Within an activity
private FragmentA fragmentA;
private FragmentB fragmentB;
private FragmentC fragmentC;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
fragmentA = FragmentA.newInstance("foo");
fragmentB = FragmentB.newInstance("bar");
fragmentC = FragmentC.newInstance("baz");
}
}
protected void displayFragmentA() {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
// removes the existing fragment calling onDestroy
ft.replace(R.id.flContainer, fragmentA);
ft.commit();
}
to this approach instead leveraging add
, show
, and hide
in the FragmentTransaction
:
// ...onCreate stays the same
// Replace the switch method
protected void displayFragmentA() {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
if (fragmentA.isAdded()) { // if the fragment is already in container
ft.show(fragmentA);
} else { // fragment needs to be added to frame container
ft.add(R.id.flContainer, fragmentA, "A");
}
// Hide fragment B
if (fragmentB.isAdded()) { ft.hide(fragmentB); }
// Hide fragment C
if (fragmentC.isAdded()) { ft.hide(fragmentC); }
// Commit changes
ft.commit();
}
Using this approach, all three fragments will remain in the container once added initially and then we are simply revealing the desired fragment and hiding the others within the container. Check out
this stackoverflow for a discussion on deciding when to replace vs hide and show.
Nesting Fragments within Fragments
Inevitably in certain cases you will want to
embed a fragment within another fragment. Since
Android 4.2, you have the ability to embed a fragment within another fragment. This nested fragment is known as a
child fragment. A common situation where you might want to nest fragments is when you are using a sliding drawer for top-level navigation and one of the fragments needs to display tabs.
Note that one limitation is that nested (or child) fragments
must be dynamically added at runtime to their parent fragment and cannot be statically added using the
<fragment>
tag. To nest a fragment in another fragment, first we need a
<FrameLayout>
or alternatively a
ViewPager to contain the dynamic child fragment in the
res/layout/fragment_parent.xml
layout:
<?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:orientation="vertical" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="I am the parent fragment" />
<FrameLayout
android:id="@+id/child_fragment_container"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
</LinearLayout>
Notice that there's a FrameLayout
with the id of @+id/child_fragment_container
in which the child fragment will be inserted. Inflation of the ParentFragment
view is within the onCreateView
method, just as was outlined in earlier sections. In addition, we would also define a ChildFragment
that would have its own distinct layout file:
// Top-level fragment that will contain the child
public class ParentFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_parent, container, false);
}
}
// Child fragment that will be dynamically embedded in the parent
public class ChildFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Need to define the child fragment layout
return inflater.inflate(R.layout.fragment_child, container, false);
}
}
Now we can add the child fragment to the parent at runtime using the getChildFragmentManager
method:
// Top-level fragment that will contain the child
public class ParentFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_parent, container, false);
}
// This event is triggered soon after onCreateView().
// onViewCreated() is only called if the view returned from onCreateView() is non-null.
// Any view setup should occur here. E.g., view lookups and attaching view listeners.
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
insertNestedFragment();
}
// Embeds the child fragment dynamically
private void insertNestedFragment() {
Fragment childFragment = new ChildFragment();
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
transaction.replace(R.id.child_fragment_container, childFragment).commit();
}
}
Note that
you must always use getChildFragmentManager
when interacting with nested fragments instead of using
getSupportFragmentManager
. Read
this stackoverflow post for an explanation of the difference between the two.
In the child fragment, we can use
getParentFragment()
to get the reference to the parent fragment, similar to a fragment's
getActivity()
method that gives reference to the parent Activity. See
the docs for more information.
Managing Configuration Changes
References