Fragments are a powerful, yet misunderstood, part of Android Development. They help us reuse components in Activities, ViewPagers, and Navigation drawers, among other advantages. On the other hand, their lifecycle, use cases, and interaction with Activities can be a complicated mess.
In this talk from 360|AnDev, take a journey through the fundamentals of Fragments, and learn how to leverage them with David Hope.
Introduction (00:00)
Fragments are a powerful, yet controversial part of Android development. They provide almost as many benefits as they do issues. We’re going to cover why fragments are used, and the different types which they come in, including the linking to the anatomy, and the infamous lifecycle.
We will take that one step further into how to use them in basic activity, along with the common usage in three primary ways, concluding with best practices.
Fragments were introduced all the way back in Android Honeycomb to help alleviate the new screen sizes that had just been added thanks to the introduction of tablets. They help us reuse components into multiple activities, providing much of the same functionality, but doing a few things better.
Types Of Fragments (01:53)
There are two main types of fragments:
- Static fragments are placed within an activity layout and never change. For example:
<?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"›
<fragment
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:id="@+id/fragment" />1
</LinearLayout>
- Dynamic fragments are almost reconfigurable puzzle pieces. They have their own lifecycle, anatomy, classes and even subtypes, with the addition of their layouts (similar to
Activity
in many respects). Using a dynamic fragment simply requires the usage of a container to place that fragment in. This is normally a framed layout within a standard activity.
Dynamic fragments also have two main subtypes:
-
Framework fragments have been around since fragment’s inception, five years ago. They are rarely updated and do not work with
AppCompatActivity
, which is the current activities version. -
Support fragments come from the version four support library and do work with
AppCompatActivities
. They are constantly updated alongside the support library, usually three or four times per year. As Android’s constantly updated, there are new features added to fragments, e.g. child fragments.
Through the support library, the older versions of Android, which would not otherwise support new features, receive them. For this reason, you almost always want to use a support library version of fragments.
Fragment Lifecycle (05:08)
Each fragment has its own lifecycle, which lives on top of the activities lifecycle. You can see a diagram on slide 17, this is complex for even the most experienced Android developers, and it is controlled by the managing activity. With a few exceptions, most of these lifecycle methods have an activity equivalent, which is where we will spend the bulk of our focus for the rest of this presentation.
onAttach (05:42)
onAttach()
is called by the managing activity when a fragment is first attach, yet it is not interactive. It’s also where you can start using the context
:
@Override
public void onAttach(Context context) {
super.onAttach(context);
}
However, it is not advised, as it may be null.
onCreateView (06:40)
Next, onCreate()
is called, followed by onCreateView()
. onCreateView()
is where you set up your user interface:
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return super.onCreateView(inflater, container, savedInstanceState);
}
First, you generally take a view (normally called a “root view”) and pass in the inflater
to inflate the layout with parameters. Then you pass in a view collection, or a view container collection, which takes in all the views and manages them, along with that bundle
, which will recreate the fragment (that you must set to false).
onActivityCreated (07:34)
Beyond the onCreateView()
, onActivityCreated()
is where the heavy lifting occurs. That is called in conjunction with an activity’s onCreate
method, so anything that you may want to do within the latter, you would do here:
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
Standard Lifecycle Methods (08:03)
After onActivityCreated()
, there are a few standard lifecycle methods similar to that of activity: onStart()
, onResume()
, and onPause()
.
onDestroyView (08:20)
In the process of the fragments being broken down, onDestroyView()
is called. That is where you clean up anything that goes on with your views to ensure that system resources themselves are saved. You want to make sure to unregister things like button clicks, text listeners or checkbox listeners in here:
@Override
public void onDestroyView() {
super.onDestroyView();
}
After onDestroyView()
, onDestroy()
is called, and finally onDetach()
.
onDetach (08:56)
onDetach()
is the last lifecycle method before a fragment is fully detached from an activity. At this point, the fragment is no longer available, and any type of call to get to that parent activity’s context through getActivity
will return null
, and cause a NullPointerException
.
@Override
public void onDetach() {
super.onDetach();
}
newInstance (09:34)
One additional part of the setup for a fragment is to create a newInstance()
method. That allows the creation of a fragment. It is where you set some default data in order to use, for example: title strings, or various pieces of primitive data, which you want to make sure is always there.
To use newInstance()
, you must always create an empty constructor. That is part of what goes into creating a fragment; similar to activity in some sense. newInstance()
takes a bundle, and uses key-value pairs to set and later get these various pieces of data in your onCreateView()
method:
public static FragmentOne newInstance() {
Bundle args = new Bundle();
FragmentOne fragment = new FragmentOne();
fragment.setArguments(args);
return fragment;
}
Once you set up all these various methods, you get to know how to use fragments. That is done with an Activity
, through a fragment manager.
Fragment Manager (11:19)
A fragment manager is this encompassing method that helps manage fragments, pulling them in and out, moving them, adding them, removing and replacing them, and even helping in restoring them.
They manage a list of fragments with activity holding a list, and help perform transactions. The calls are changed using a fluent interface, which keeps you from creating unnecessary multiple fragment managers to do so.
FragmentManager fm = getSupportFragmentManager();
Each FragmentManager
has an equivalent with the type of fragment that you use. If you use framework fragments, it would be getFragmentManager()
. In the case of support fragments, it is getSupportFragmentManager()
.
Fragment ID, Fragment TAG (12:12)
A fragment manager must know how to identify which fragment it is using, and it does that through either a fragment ID or tag. These work similar to findViewById()
, to help determine which fragment itself to pull in for transactions.
findFragmentById()
takes in the container layout’s ID, unlike what you may expect, like the fragment’s ID. The findFragmentByTag()
, on the other hand, takes in a string constant, to determine which fragment is used (like the tag you use in debugging).
Fragment Transaction (12:59)
Transactions are the series of actions which bring your fragment in, out, or replacing them. They always start off with a beginTransaction()
method, called by the FragmentManager
. They are usually followed by a series of actions: add
, replace
, or others. There can be multiple actions as part of a transaction. They are always concluded with a commit()
to ensure that every action has been committed.
This is a basic add
example using our FragmentManager
(fm
):
fm.beginTransaction()
.add(R.id.fragment_list_container, fragmentOne)
.commit();
Common Usage (15:02)
With this basic setup, you can now unleash the power of fragments, through three primary ways:
- Tablets
- Tab layouts
- Dialog fragments
Common Usage - Tablets (15:04)
Tablets have all this extra real estate space you want to work with. They are where you can bring in multiple fragments at a single time. We can see this in a common design pattern called “Master Detail Flow”.
For example, in email apps where you have a list of items. On a phone app, you might have a view with a list and some details about each item, and then a more detailed view when you select one of them. However, in tablets you want these to be side-by-side, in order to maximize the usage of your real estate.
Extending your application to use fragments, to use all that extra real estate, requires extra work. Generally, a layout with a specific “Resource Qualifier”, and a condition to determine which type of device you are on.
Resource Qualifier (16:22)
Resource Qualifier helps you change the configuration gap based upon certain conditions (e.g. language, day or night mode, screen width or height). For tablets, this is done through a screen width resource qualifier, which sets an absolute minimum width a screen must be for a certain condition to be true.
It is generally considered best practice to set up a boolean with a default value with no resource qualifier, with a name such as isTablet = false
, and then add a resource qualifier with a screen width of 600. This will set the apps minimum width, and ensure that you use the correct layout for your application.
Pulling that into your activity is simple. You can then create a private boolean with a similar name:
boolean isTablet = getResources().getBoolean(R.bool.is_tablet);
And then checking it within your application:
if (isTablet) {
fm.beginTransaction()
.add(R.id.tablet_list_container, fragmentOne)
.commit();
fm.beginTransaction()
.add(R.id.tablet_item_detail_container, fragmentTwo)
.commit();
}
Common Usage - Tab Layouts (18:13)
Tab layouts are common and powerful. They allow you to easily switch between various screens, each of which is generally united to each other; each screen is a fragment. Tab layouts come from the Design Support library, which you must manually import to your application.
There are several different forms they come in: fixed tabs or sliding tabs. Personally, fixed tabs are a better user experience pattern, because sliding tabs can otherwise get lost within phones. Tab layouts are just one part of the piece regarding how to use this design pattern, which works alongside a View Pager.
View Pager (19:28)
A View Pager allows you to easily and quickly swipe between these various tabs without ever touching the titles or the icons. They come from the version four support library and are setting in the activity’s layout along with a tab layout. They can be used as a view or RecyclerView in some sense.
However, that is still not all that it takes to use this design pattern. And that is where Pager Adapters come into play.
Pager Adapter (20:10)
A Pager Adapter manipulates pages to use, e.g. the fragments themselves, and the View Pager and the tab layout know which fragment a user is on at a given time. They can be set in their own class, or within an anonymous inner class (e.g. an on-click listener for buttons).
They come in two main types: FragmentPagerAdapter and FragmentStatePagerAdapter. Both of these have a getCount()
method, which returns the number of fragments. And they also have a getItem()
method, which takes the position of each fragment, replacing it with instance of each fragment, and are managed through a fragment manager as part of their constructor. That is where the similarities end.
Fragment Pager Adapter keeps all pages in memory at a single time, and never fully removes them. This can be problematic for any memory intensive application. That is where the Fragment State Pager Adapter comes into play. It only manages the single page a user is on at a time. It will fully remove the fragment, and it does not take up unnecessary memory. Later recreating it through newInstance
, or savedInstanceState
.
For anything that is even remotely intensive, Fragment State Pager Adapter is the route to go.
To set up this design pattern, you need two things. Setting your View Pager and your tablet out like views, and your activity, passing in the statePagerAdapter
:
ViewPager.setAdapter(statePagerAdapter);
Then, calling:
tabLayout.setupWithViewPager(ViewPager)
To connect the tabLayout
to the ViewPager
.
Common Usage - Dialog Fragments (22:42)
Dialog fragments work along with alert dialogs. They easily restore the state of a dialog - for example, in the event a device is rotated, it does not disappear. They have their own OnCreateView()
method, which allows you to customize them, along with onCreateDialog()
method:
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder dialog = new AlertDialog.Builder(getActivity());
dialog.setTitle(R.string.dialog_fragment_title)
.setMessage(R.string.dialog_fragment_message)
.setPositiveButton(android.R.string.yes, null)
.setNegativeButton(androld.R.string.no, null);
AlertDialog builder = dialog. create();
builder.show();
return dialog.create();
}
Setting up a dialog fragment is relatively simple, with a few key differences as compared to working with a single fragment:
FragmentManager fm = getSupportFragmentManager();
DialogFragmentEx dialogFragmentEx = new DialogFragmentEx();
dialogFragmentEx.show(fm, FRAGMENT_TAG);
Beyond using your FragmentManager
and creating an instance of a DialogFragment
, you must use the show()
method of a dialog fragment to show the AlertDialog
. It receives the fragment manager and the fragment tag.
Odds and Ends (24:21)
There are a few odds and ends (which I did not get to cover today in detail) that are worth mentioning:
- A navigation drawer is part of the infamous hamburger menu, which partially obscures your activity or fragment to reveal a way to quickly navigate between various other screens.
- Child fragments are fragments nested inside of other fragments. They have their own unique version of the fragment manager and can be a little difficult for beginners to use.
- Headless fragments are fragments without a user interface. They are used to perform ongoing operations in the background, with similar functionality (e.g. as a service or other background threads). I would recommend you not to ever use headless fragments.
Best Practices (25:45)
There are a few best practices to follow:
- For any single activity, keep a maximum of three fragments. This will help you keep the management of transactions down (and save many headaches later on).
- For fragments in tab layouts, five fragments maximum is a good pattern to follow; otherwise your user interface becomes cluttered.
- For any type of interaction between activity and a fragment, create an interface within your fragment for the activity override. This is especially useful for button clicks, which would bring another fragment on the screen. Fragments themselves should never know about each other since they are composable components and could be in any given activity at a time.
- If you do use fragments, create them early. Trying to break down the multiple activities of the fragments is a torture.
In sum, fragments are good for many reasons: they have their own unique lifecycle, different types, and multiple use cases. If you are not using fragments already, I hope that you will reconsider after learning more about them.
Q & A (27:50)
Q: Why do you recommend not using headless fragments? David: I do not think that it offers many benefits; I think it is better to go that way and use something like a dedicated service, or another option. And they can be retained, which is itself an issue in fragments.
Q: Do you think fragments could be replaced by custom views? David: Custom views still have their own place in this world. If you were trying to replace an entire screen with custom views, it is better off to use a fragment, as you are recreating the same. However, if you are just creating a custom part of the screen, such as RecyclerView, use custom views.
Q: What are the best practices for navigation between fragments? David: There is an addToBackStack
method, which helps you - you can pass in null
as the value. It is better to pass in the unique tag to bring that back; you can tell where it is later on. Otherwise null
just becomes problematic.
Receive news and updates from Realm straight to your inbox