Chapters

Hide chapters

Android Apprentice

Fourth Edition · Android 11 · Kotlin 1.4 · Android Studio 4.1

Section II: Building a List App

Section 2: 7 chapters
Show chapters Hide chapters

Section III: Creating Map-Based Apps

Section 3: 7 chapters
Show chapters Hide chapters

11. Using Fragments
Written by Darryl Bayliss

Thanks to the standard set of hardware and software features Android includes across devices, adding new features to your app is easy. When it comes to designing an appealing user interface adapting across devices with varying screen sizes, things can get tricky!

In this chapter, you’ll adapt Listmaker to make full use of the additional screen space a tablet provides. Along the way, you’ll also learn:

  • What Fragments are and how they work with Activities.
  • How to split Activities into Fragments.
  • How to provide different Layout files for your app depending on the device’s screen size.

Getting started

If you’re following along with your own app, open it and keep using it with this chapter. If not, don’t worry. Locate the projects folder for this chapter and open the Listmaker app inside the starter folder.

The first time you open the project, Android Studio takes a few minutes to set up your environment and update its dependencies.

You’ll start off by creating a virtual device to emulates a tablet. If you have a physical tablet available, you can use that if you prefer.

With the Listmaker project open, click Android Virtual Device Manager along the top of Android Studio.

The AVD window pops up, showing you the emulators already available on your machine.

Click Create Virtual Device at the bottom left of the window.

A new window pops up asking what hardware you want the virtual device to emulate.

Select the Tablet category on the left. Notice the table in the middle of the window changes to offer a selection of tablets.

Select Pixel C. Then, in the bottom-right, click Next to show the next screen.

This screen asks what version of Android you want the device to run. Select the release name titled R and click Next.

Note: You may need to download the Android image before selecting the Android version.

If so, don’t worry. Just click the download button next to the release name, Android Studio will display a new window to show the download progress.

Once the download is complete. Press the Finish button to return back to the previous screen. Then, select Android Q and click Next.

The final screen displays the configuration for the device, allowing you to tweak properties like the device name, the orientation of the device on startup, and a range of advanced settings. Don’t worry about changing anything. Click Finish to complete setting up the emulator.

Run your app using the new emulator. Close the AVD window, then next to the run app button at the top of Android Studio, select the new device using the dropdown.

Next, click the run button. The tablet emulator will begin to load. Once the tablet has started and the app loads, you’ll see that it looks exactly as it did on the phone.

Note: For Android Pie devices and above, you may need to enable auto-rotate on your device or emulator if the screen doesn’t rotate automatically.

To do this, swipe the notification drawer down to reveal the quick settings and ensure the auto-rotate button is not grayed out. Tap on it to enable auto-rotation if it is.

Try out using Listmaker on a bigger device. Create some lists and add tasks to each one, taking note of the extra real estate in the app.

Although the app works on a tablet, its design isn’t optimized for the extra space available on the screen. That’s your main task for this chapter, you need to consider how to make your app adapt to the size of a device’s screen.

The good news is you’ve already done most of the hard work in the last few chapters by using Fragments. It’s finally time to talk about them and learn what they are.

What is a Fragment?

In Section 1, you built the UI for Timefighter using a single Activity. If you continued to build the app, you may have gotten into a situation where you wanted to show the same UI across different Activities. You could have copied the UI across different Activities, but that defeats one of the core rules of programming. Don’t repeat yourself, or DRY for short. That’s where Fragments help.

A Fragment is used to break the UI into smaller pieces, or Fragments if you will! These Fragments can be reused across your App, so you don’t need to recreate the same UI again and again. Activities still play a key role, as Fragments can only exist within an Activity.

Since it’s unknown whether a Fragment will be displayed at runtime, it’s important that they are as self-contained as possible and don’t directly reference other Fragments or Activities. This is another general rule of programming, keeping objects loosely coupled.

Note: In computing and systems design, a loosely coupled system is one in which each of its components has, or makes use of, little or no knowledge of the definitions of other separate components. https://en.wikipedia.org/wiki/Loose_coupling.

Similar to Activities, Fragments can have their UI defined via Layouts and even have their own Lifecycle. Fragments can also be dynamically added or removed, giving your Activities enormous flexibility.

You’ll see that flexibility in action for this chapter. You’re going to show the MainFragment and ListDetailFragment together in MainActivity when Listmaker runs on a Tablet.

Note: If you want to read more about Fragments, read the official documentation available at https://developer.android.com/guide/components/fragments.html.

With the theory out of the way, you’re ready to make Listmaker Tablet ready!

Creating a Layout for Tablets

In summary. You want to make sure MainActivity.kt is able to:

  1. Show the new Fragments at the right time.
  2. Provide information to each Fragment.
  3. Let your app shift its appearance depending on the device.

First, you need to create a Layout that works for a large screen. In the Project navigator, right-click layout, then select New ▸ Layout resource file.

You’re going to create a new Layout file, with a tiny difference. You’re creating a version of the main_activity.xml Layout that only displays on large screens.

This gives you the option to customize the UI for various sizes of screens. Android is even intelligent enough to automatically choose which Layout it should use as well. Very helpful!

For the File name, name your Layout main_activity. Then, in the Available qualifiers list in the bottom-left of the window, select the size option and click >>.

From here, you can select various screen sizes to determine which sizes your Layout will use. You want the layout to be used by tablet-sized screens, so in the screen size dropdown, choose X-Large.

Click OK in the bottom-right and Android Studio creates the new Layout. Take a moment to look at the Project navigator to the left:

Android Studio now shows both the Layout files together in a drop-down, and even shows the qualifier you set to distinguish between the two. Now, you just have to populate it with the Layout you’d like.

You’re going to use the Text editor for this, as it’s faster for this task. In main_activity.xml (xlarge), ensure the Code editor is shown at the top right of the Layout editor window.

Replace the existing XML with the following code for the entire layout, so it contains a ConstraintLayout, Two FragmentContainerView’s and a FloatingActionButton:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/detail_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context="com.raywenderlich.listmaker.MainActivity"
    tools:showIn="@layout/main_activity">

    <!-- 1 -->
    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/main_fragment_container"
        android:name="com.raywenderlich.listmaker.ui.main.MainFragment"
        android:layout_width="300dp"
        android:layout_height="match_parent"
        android:layout_marginStart="0dp"
        android:layout_marginTop="0dp"
        android:layout_marginBottom="0dp"
        android:layout_weight="1"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@id/list_detail_fragment_container"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <!-- 2 -->
    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/list_detail_fragment_container"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_weight="2"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="1.0"
        app:layout_constraintStart_toEndOf="@+id/main_fragment_container"
        app:layout_constraintTop_toTopOf="parent" />
    
    <!-- 3 -->
    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/fabButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_marginEnd="16dp"
        android:layout_marginBottom="16dp"
        android:clickable="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:srcCompat="@android:drawable/ic_menu_add" />

</androidx.constraintlayout.widget.ConstraintLayout>

ConstraintLayout and FloatingActionButton should be familiar to you now. But what’s going on with the FragmentContainerViews?

FragmentContainerViews are a dedicated View to hold Fragments. Up until now, you’ve used FrameLayouts to contain a Fragment.

From now, however, you should use FragmentContainerViews to show Fragments. They contain specialized code to help Fragments work correctly.

When the larger layout is shown on a large screen, you need both the ListSelectionFragment and ListDetailFragment to make use of the extra space.

The FragmentContainerView with the id main_fragment_container will hold the MainFragment and is added by MainActivity in the onCreate() method. You’ll update that method shortly.

The other FragmentContainerView with the id list_detail_fragment_container, will hold the ListDetailFragment. This Fragment appears on the screen when a list is created or tapped. The difference with this layout is the list detail Fragment appears in the same Activity, without the need to launch another using an Intent.

The FloatingActionButton will serve two purposes. The first is to create a list. Then, when the list detail Fragment is shown, will change to add tasks to the Fragment.

Next, you need to change MainActivity.kt to handle both Layouts, depending on the size of the device screen Listmaker is running on. The first thing you need is a way to know if you’re using the larger layout.

Open MainActivity.kt, then in onCreate(savedInstanceState: Bundle?) update the savedInstanceState if check to use the right View to contain MainFragment, depending on what Layout is used:

if (savedInstanceState == null) {

  // 1
  val mainFragment = MainFragment.newInstance()
  mainFragment.clickListener = this

  // 2
  val fragmentContainerViewId: Int = if (binding.mainFragmentContainer == null) {
    R.id.detail_container
  } else {
    R.id.main_fragment_container
  }
  
  // 3
  supportFragmentManager.commit {
    setReorderingAllowed(true)
    add(fragmentContainerViewId, mainFragment)
  }
}

Here is what the code does:

  1. When the Activity is first created, you create a new instance of MainFragment(). Then, you set the clickListener of the Fragment to be the Activity. Don’t worry about the red lines for now, you’ll update the Fragment soon.

  2. You create a variable to hold the id of the FragmentContainerView. Depending on the Layout used, the id can be different. To make sure the Activity uses the right one, the ViewBinding is checked to see if mainFragmentContainer is null. If the Container is null, then the smaller Layout is used by the Activity and the id for the FrameLayout is assigned to the variable. If it isn’t, then the id for the FragmentContainerView created earlier is used.

  3. With the id known for what View to show the Fragment in. The next lines setup the View to show the Fragment using the supportFragmentManager. This is a property provided by the Activity, its role is to handle presenting and removing Fragments. The commit method performs the hard work. The methods inside the lambda are done using a FragmentTransaction. In this case, when commit is called, the MainFragment is added to the container view identified by the view Id.

Note: You may also notice another method inside the commit method called setReorderingAllowed(true). This is required to ensure the FragmentTransaction works correctly. You can learn more about FragmentTransaction over at https://developer.android.com/guide/fragments/transactions

Next, you need to update MainFragment to ensure it is fully decoupled from MainActivity. At the moment, the constructor of MainActivity requires a listener to be passed. You’ll change that so the listener can be assigned after the Fragment is created.

Open MainFragment.kt, then update the primary constructor so it is empty:

class MainFragment : Fragment(), ListSelectionRecyclerViewAdapter.ListSelectionRecyclerViewClickListener {

Next, move the clickListener into the class so it exists as a property. Set the property as lateinit so it can be assigned a value after it is created:

lateinit var clickListener: MainFragmentInteractionListener

Finally, update the newInstance() method inside the companion object so it uses the empty constructor:

  companion object {
    fun newInstance() = MainFragment()
  }

Run the app using the Tablet emulator. Then, tap the FloatingActionButton. If all is well, the AlertDialog will appear to ask for the name of the List. Enter the title and tap create. The list will be created, then the ListDetailActivity appears. Tap back to get back to the MainActivity, your list will be there:

It’s not working 100%, but it’s progressing at least. The next step is to stop Listmaker from showing the ListDetailActivity when using the larger Layout and display it in MainActivity instead. You’ll do that in the next section

Adding the ListSelectionFragment to MainActivity

In MainActivity, update showListDetail(list: TaskList):

private fun showListDetail(list: TaskList) {

  if (binding.mainFragmentContainer == null) {
    val listDetailIntent = Intent(this, ListDetailActivity::class.java)
    listDetailIntent.putExtra(INTENT_LIST_KEY, list)
    startActivityForResult(listDetailIntent, LIST_DETAIL_REQUEST_CODE)
  } else {
    val bundle = bundleOf(INTENT_LIST_KEY to list)
    supportFragmentManager.commit {
      setReorderingAllowed(true)
      replace(R.id.list_detail_fragment_container, ListDetailFragment::class.java, bundle, null)
    }
  }
}

Similar to onCreate(), you check the binding to see if the FragmentContainerView from the larger layout is instantiated. If it’s null, then ListDetailActivity is created as usual with the list passed in via an Intent. Otherwise, a Bundle is created containing key values. In this case, the key is INTENT_LIST_KEY and the value is the list selected from the RecyclerView. Finally, ListDetailFragment is set on the other FragmentViewContainer using the supportFragmentManager, passing in the bundle.

Notice the method used is called replace. This creates a new Fragment every time it is called. This works here because you want a Fragment every time a new list is tapped in MainFragment.

Open ListDetailFragment, then in onActivityCreated(savedInstanceState: Bundle?) add the following line below getting the viewModel.

val list: TaskList? = arguments?.getParcelable(MainActivity.INTENT_LIST_KEY)
if (list != null) {
  viewModel.list = list
  requireActivity().title = list.name
}

You acquire the list by grabbing the bundle passed in from MainActivity, using the arguments method. Then, getParcelable() is used to acquire the list. The key INTENT_LIST_KEY is used to get the list. You check to make sure the list isn’t null, before assigning it to the viewModel. You also set the title of the Activity to the list name.

With that done, your Fragment is ready to be used. There are a few last things to do, hook up the FloatingActionButton to add tasks when the ListDetailFragment is visible. Then finally to stop showing the list detail when the back button is pressed.

Wiring Up the FloatingActionButton

In MainActivity.kt. You must change the behavior of the FloatingActionButton when adding tasks to a list.

To do that, add the missing showCreateTaskDialog method to MainActivity.kt:

private fun showCreateTaskDialog() {
  val taskEditText = EditText(this)
  taskEditText.inputType = InputType.TYPE_CLASS_TEXT

  AlertDialog.Builder(this)
          .setTitle(R.string.task_to_add)
          .setView(taskEditText)
          .setPositiveButton(R.string.add_task) { dialog, _ ->
            val task = taskEditText.text.toString()
            viewModel.addTask(task)
            dialog.dismiss()
          }
          .create()
          .show()
}

When the method is called, an AlertDialog is created with an EditText attached. The EditText is where you can add your task. When the positive button is pressed, the task is taken from the EditText and added to the viewModel.

There is a problem though. MainViewModel doesn’t know how to handle adding tasks to a list. MainViewModel also isn’t used by ListDetailFragment, so it won’t know when a task is added.

The solution here is to expand MainViewModel to contain the fields from ListDetailViewModel, then update all Activities and Fragments to rely on MainViewModel. Why are we doing this?

If you recall, each ViewModel is scoped to the lifetime of its Activity. Now that Listmaker is showing two Fragments in the same Activity, it can now provide the same ViewModel to both Fragments. The Activity can also communicate with both Fragments using the ViewModel, all while keeping both Fragments independent of each other.

Let’s begin. Open MainViewModel, then add the following lambda and method from ListDetailViewModel:

lateinit var list: TaskList

lateinit var onTaskAdded: (() -> Unit)
  
fun addTask(task: String) {
  list.tasks.add(task)
  onTaskAdded.invoke()
}

The code will be used by ListDetailFragment, they’re identical to the same code in ListDetailViewModel. Next, open ListDetailActivity and change the type of the viewModel at the top of the class:

lateinit var viewModel: MainViewModel

In onCreate(savedInstanceState: Bundle?), update the initialisation of viewModel to create a MainViewModel:

viewModel = ViewModelProvider(
  this,
  MainViewModelFactory(PreferenceManager.getDefaultSharedPreferences(this))
).get(MainViewModel::class.java)

Next, open ListDetailFragment and repeat the process. Update the viewModel type to MainViewModel, then change the assignment of viewModel in onActivityCreated(savedInstanceState: Bundle?)

viewModel = ViewModelProvider(
        requireActivity(),        MainViewModelFactory(PreferenceManager.getDefaultSharedPreferences(requireActivity()))
)
.get(MainViewModel::class.java)

It’s important that requireActivity() is used, as it ensures the same instance of MainViewModel is returned. With the ViewModel updated it’s time to turn back to the FloatingActionButton. Open MainActivity again, then in showListDetail(list: TaskList) add the following line underneath the supportFragmentManager transaction.

binding.fabButton.setOnClickListener {
  showCreateTaskDialog()
}

The button onClickListener now changes to create a task, whenever a ListDetailFragment is shown.

The last thing to do is to reset the button to create lists again when the back button is pressed. Override onBackPressed() so the Activity knows how to deal with the back button being pressed when using Fragments.

override fun onBackPressed() {

  // 1
  val listDetailFragment =
    supportFragmentManager.findFragmentById(R.id.list_detail_fragment_container)

  // 2
  if (listDetailFragment == null) {
    super.onBackPressed()
  } else {
    // 3
    title = resources.getString(R.string.app_name)

    // 4
    supportFragmentManager.commit {
      setReorderingAllowed(true)
      remove(listDetailFragment)
    }
    
    // 5
    binding.fabButton.setOnClickListener {
      showCreateListDialog()
    }
  }
}

Going through the method:

  1. Use supportFragmentManager to find the ListDetailFragment shown. You use the view Id of the fragmentContainView to acquire the Fragment.

  2. Since the Fragment may not exist, check to see if the Fragment is null. If it is, then call super.onBackPressed(). This closes the Activity.

  3. If the Fragment exists, then you begin to reset the Activity to its original state. First, the title of the Activity is changed back to Listmaker.

  4. Use supportFragmentManager to commit another transaction. This time to remove the ListDetailFragment.

  5. Finally, reset the FloatingActionButton to create a list when it is tapped.

With that done, you’re ready to see all your hard work in action! Run your app on a phone-sized device and start creating lists. Listmaker should work just as before.

Run your app on a tablet-sized device. Tap the FAB to create a list, then when the title changes to the name of the list. Tap the FAB again to add a task.

Your app now displays two different screens, at the same time, making use of all the available space!

Key Points

Fragments are powerful components in Android. They have become a major part of Android over the years and you would do well to learn all you can about them. In this chapter, you’ve learned:

  • How to create the same layout for different-sized device screens.
  • What Fragments are and the purpose they serve in Android.
  • How to add FragmentContainerViews to hold your Fragments.
  • How to use supportFragmentManager to add and remove Fragments.
  • How to use ViewModels to communicate across Fragments.

Where to go from here?

What you’ve encountered here is a brief dip into the benefits they can provide. Any app wanting to succeed across multiple-sized devices needs Fragments to ensure it provides the best experience for its users. If you’d like to know more about the Fragments, the Android developer pages have comprehensive information on Fragments over at https://developer.android.com/guide/fragments.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.