ZOFTINO.COM android and web dev tutorials

Android Fragments Tutorial

You can divide your activity behavior and user interface into multiple components so that certain components in an activity can be reused and replaced with new components to create a flow of an activity or to perform a task. These components which are part of an activity are called Fragments.

You can build an application consisting of few activities with each activity representing a task consisting of multiple Fragments with each Fragment representing certain behavior in the flow of the task. Like Activity, Fragment has its own lifecycle and you need to define layout for each fragment.

Table of Contents

Creating Fragment Layout

First create fragment layout with UI elements defined in it. Defining fragment layout is similar to how layout is defined for activity.

 <?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">
    <EditText
        android:id="@+id/search"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <Button
        android:id="@+id/submit"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Submit"/>
</LinearLayout>

Creating Fragments

Fragment can be created by extending Fragment or one of its subclasses such as DialogFragment, HeadersSupportFragment, ListFragment, PlaybackSupportFragment, PreferenceFragmentCompat, RowsSupportFragment, SearchSupportFragment, etc. If you want backward compatibility, you can create fragment using Fragment class from support library.

Next you need to implement onCreateView callback method. In this method, you can either instantiate user interface using LayoutInflater and Fragment layout xml or create Fragment user interface programmatically and you can add event listeners to Views and set configuration of Views. This method should return the view create for fragment user interface.

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

Adding Fragment to Activity

The important step that needs to be followed in order to use Fragments is creating activity that extends FragmentActivity or its subclass AppCompatActivity to have necessary framework to handle fragments in your activity.

You can add a Fragment to Activity by defining it in layout or by creating it programmatically. To add fragment to layout, you need to define fragment element and set name attribute to fragment class.

 <?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">
    <fragment android:name="com.zoftino.android.SampleFragment"
            android:id="@+id/sample"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
</LinearLayout>

Another way of adding a fragment to an activity is by defining a ViewGroup in the activity layout and adding Fragment to it programmatically in your activity. To add fragment to ViewGroup, first get FragmentManager by calling getFragmentManager() or getSupportFragmentManager() if your fragment extends Fragment class from support library. Then get FragmentTransaction object and call add or replace on FragmentTransaction object passing the view group id and fragment instance.

 FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.add(new SampleFragment(), "SAMPLE_FRAG");
ft.commit();
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.sample_content, new SampleFragment());
ft.commit();

Removing Fragment from Activity

To remove a fragment, first get Fragment object by calling findFragmentByTag method on FragmentManager passing fragment tag specified when it was added, then get FragmentTransaction object and call remove on FragmentTransaction object passing the fragment object.

 Fragment fragment = getSupportFragmentManager().findFragmentByTag("SAMPLE_FRAG");
if(fragment != null)
    getSupportFragmentManager().beginTransaction().remove(fragment).commit();

Fragment Lifecycle

Similar to the activity, Fragment exists in three states: resumed state, paused state and stopped state. In resumed state, the fragment is visible in foreground activity. A fragment will be in paused state when the fragment and container activity is visible but not in the foreground because the focus is on another activity which is transparent. A fragment will be in stopped state when the fragment is not visible either because the container activity is not visible or the fragment has been removed by the activity and added to back stack. In the stopped state, fragment instance is retained by the system.

Lifecycle of the fragment is similar to the activity lifecycle with few extra callback methods to handle interaction with the activity.

When Fragment is created, onAttach(), onCreate(), onCreateView(), onViewCreated() and onActivityCreated() callback methods are called. Callback method onAttach() is called when a fragment is first attached to context. Callback method onCreateView gives the opportunity to instantiate fragment user interface. In callback method onViewCreated (), you can initialize views and apply saved sate in the case of fragment being recreated. Callback method onActivityCreated() is called after activity has been created and fragment’s view hierarchy is instantiated, it give a chance to do the final initialization.

Below is the order in which Fragment callback methods are called when fragment is created and added to an activity regardless of how it is added to the activity. Meaning same order is followed when the fragment is added to the activity by defining fragment in the activity xml or Fragment is added or replaced using FragmentTransaction.

Callback method onStart is called when the fragment is created or every time the fragment comes to foreground. Callback method onResume is called when fragment is visible but not in foreground. These call back methods onStart and onResume are called when corresponding methods in the activity are called.

onAttach()
onCreate()
onCreateView()
onViewCreated()
onActivityCreated()
onStart()
onResume()

When fragment is destroyed callback methods onDestroyView(),onDestroy() and onDetach() are called. Callback method onDestroyView() is called when the fragment view is detached from fragment. Callback method onDetach() is called when the fragment is detached from the activity. Callback method onDestroy() is called before the fragment is destroyed.

Callback method onPause is called when fragment is killed or every time the fragment becomes inactive but still visible. Callback method onStop is called when fragment moves to background. These call back methods onPause and onStop are called when corresponding methods in the activity are called.

Below is the order in which callback methods are called when fragment is destroyed for example when screen orientation is changed.

onPause()
onStop()
onDestroyView()
onDestroy()
onDetach()

Event Handling in Fragments

You can handle input events from views in the user interface of fragments. Similar to input events handling in the activity, to handle input events in fragments, first get the view and add listener to it. For example, in onCreateView method, find button object by calling findViewById on the instantiated View object and add listener to it as shown below.

 @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    Log.d("Fragment ","onCreateView");
    View view = inflater.inflate(R.layout.sample_fragment,
            container, false);

    ((Button)view.findViewById(R.id.submit)).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            Toast.makeText(getActivity(), "submit clicked", Toast.LENGTH_LONG).show();
        }
    });
    return view;
}

Back Stack Behavior with Fragments

To add Fragment transitions to back stack, you need to call, while adding, removing or replacing fragments, addToBackStack method on FragmentTransaction before calling commit. This way, when user clicks back button, it takes the user back through history of fragments.

ft.replace(R.id.sample_content, new SampleFragment());
ft.addToBackStack(null);
ft.commit();

Fragment and Activity Communication

Fragment can access activity by calling getActivity() method and perform actions such as calling methods in activity, getting data from activity by calling a method in the activity and finding views in the activity layout.

 TextView txtView = getActivity().findViewById(R.id.textview);
DataObject do = ((FragmentActivity)getActivity()).getData();
Activity can also get the reference to Fragment object and call its methods. To get data from fragment, activity can call a method in fragment which returns data.
 SampleFragment fragment = (SampleFragment)getFragmentManager()
					.findFragmentById(R.id.sample); 

Sending Data to Fragment from Activity

One way to get data from activity is by calling a method on the activity that returns data as shown above. Data can also be sent to fragment when it is created by adding data to bundle.

 Bundle bundle = new Bundle();
bundle.putString("user", "user name");
SampleFragment fragment = new SampleFragment();
fragment.setArguments(bundle);

Read the data in onCreateView method of the fragment by calling getArguments() method to get the bundle and calling appropriate methods on it to read values from it.

 @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
    String user = getArguments().getString("user");    
    return inflater.inflate(R.layout.fragment, container, false);
} 

Sending Data from Fragment to Activity

Fragment can send data to activity by calling a setter method in the activity.

((FragmentActivity)getActivity()).setData("333");

Fragment and Fragment Communication

Since Fragments should be independent, Fragment to fragment communication should not be direct instead it should be done via host activity. To send data from one fragment to another fragment, Fragment should get host activity object and call one of its methods to send data to other fragment, then the activity in turn should obtain the second fragment object and call its method passing the data received from fragment one.

In order to prevent tight coupling between fragment and activity, we should use interface which defines methods for data communication from source fragment to activity.

In the example below, search activity contain two fragments, search and search results fragments. Search fragment passes results to search results fragment via activity.

Interface

 public interface SearchInterface {
    public void setResult(String s);
}

Activity

 public class SearchActivity extends AppCompatActivity implements SearchInterface{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_search);

        FragmentManager fm = getSupportFragmentManager();
        FragmentTransaction ft = fm.beginTransaction();
        ft.replace(R.id.search, new SearchFragment());
        ft.replace(R.id.search_result, new SearchResultsFragment());
        ft.addToBackStack(null);
        ft.commit();
    }
    public void setResult(String str){
        SearchResultsFragment sf = (SearchResultsFragment)getSupportFragmentManager()
                .findFragmentById(R.id.search_result);
        sf.setResult(str);
    }
} 

Activity Layout

 <?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/search"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="16dp"
    android:layout_marginLeft="8dp"
    android:layout_marginStart="8dp"/>
    <FrameLayout
        android:id="@+id/search_result"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:layout_marginLeft="8dp"
        android:layout_marginStart="8dp"/>
</LinearLayout>

Search Fragment

 public class SearchFragment extends Fragment {
    private EditText et;
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.search_fragment,
                container, false);
        et = view.findViewById(R.id.search);
        ((Button)view.findViewById(R.id.submit)).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                ((SearchInterface)getActivity()).setResult("results "+et.getText().toString());
            }
        });
        return view;
    }
}
 

Search Fragment Layout

<?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">
    <EditText
        android:id="@+id/search"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <Button
        android:id="@+id/submit"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Submit"/>
</LinearLayout>
 

Search Results Fragment

 public class SearchResultsFragment extends Fragment {
    private TextView tv;
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.search_result,
                container, false);
        tv = view.findViewById(R.id.search_results);
        return view;
    }
    public void setResult(String result){
        tv.setText(result);
    }
}
 

Search Results Fragment Layout

 <?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/search_results"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

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.search, 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.search, f);
ft.addToBackStack(null);
ft.commit();

Fragments Custom Animations

You can define animation in xml and use it for fragment transitions. To add custom animation, you need to call setCustomAnimation method on fragment object passing resource ids of enter and exit animations.

You need to define animation xml resources for fragment enter and exit transitions and save them in the res/anim folder.

Below xml defines scale animation.

<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:anim/bounce_interpolator"
    android:shareInterpolator="false">
    <scale
        android:fromXScale="0.1"
        android:toXScale="1.0"
        android:fromYScale="0.5"
        android:toYScale="1.0"
        android:pivotX="0.5"
        android:pivotY="0.5" />
</set>

Below xml defines translate animation.

<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:anim/accelerate_interpolator"
    android:shareInterpolator="false">
    <translate android:fromXDelta="-50%"
        android:toXDelta="60%"
        android:fromYDelta="-50%"
        android:toYDelta="60%"
        android:duration="2000"/>
</set>
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.setCustomAnimations(R.anim.translate_anim, R.anim.scale_anim);
ft.replace(R.id.search, new SampleFragment());
ft.addToBackStack(null);
ft.commit();

Fragment Examples

Below some of the example which use fragments.