Android Fragments Tutorial: An Introduction with Kotlin

In this Android Fragments with Kotlin tutorial you will learn the fundamental concepts of fragments while creating an app that displays dogs breeds. By Aaqib Hussain.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

Creating a Fragment

Eventually, El Dogo will show a list of dogs breeds on launch and tapping on any of the items will display details about that particular breed. To start, you’ll work backwards and first create the detail page.

Open the starter project in Android Studio and find fragment_dog_details.xml under app ▸ src ▸ main ▸ res ▸ layout. This XML file lays out the dog detail display. It also displays one of the drawable resources and the associated String resources.

Dog detail fragment layout

Locate the DogDetailsFragment.ky file in the project. This class will be responsible for displaying details for a selected breed.

In DogDetailsFragment.kt, the code now looks like this:

import android.os.Bundle
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup

//1
class DogDetailsFragment : Fragment() {

  //2
  companion object {

    fun newInstance(): DogDetailsFragment {
      return DogDetailsFragment()
    }
  }

  //3
  override fun onCreateView(inflater: LayoutInflater, 
                            container: ViewGroup?, 
                            savedInstanceState: Bundle?): View? {
    return inflater.inflate(R.layout.fragment_dog_details, container, false)
  }

}

This code:

  1. Declares DogDetailsFragment as a subclass of Fragment. Notice the import android.support.v4.app.Fragment: This is from the v4 support library mentioned before.
  2. Provides a method for creating new instances of the fragment, a factory method.
  3. Creates the view for the fragment.

Activities use setContentView() to specify the XML file that defines their layouts, but fragments create their view hierarchy in onCreateView(). Here you called LayoutInflater.inflate to create the hierarchy of DogDetailsFragment.

The third parameter of inflate specifies whether the inflated fragment should be added to the container. The container is the parent view that will hold the fragment’s view hierarchy. You should always set this to false when inflating a view for a fragment: The FragmentManager will take care of adding the fragment to the container.

Using a factory method

There’s a new kid in town here: FragmentManager. Each activity has a FragmentManager that manages its fragments. It also provides an interface for you to access, add and remove those fragments.

You’ll notice that while DogDetailsFragment has a factory instance method, newInstance(), it does not have any constructors.

Wait, why do you need a factory method but not a constructor?

First, because you did not define any constructors, the compiler automatically generates an empty, default constructor that takes no arguments. This is all that you should have for a fragment: no other constructors.

Second, you probably know that Android may destroy and later re-create an activity and all its associated fragments when the app goes into the background. When the activity comes back, its FragmentManager starts re-creating fragments by using the empty default constructor. If it cannot find one, you get an exception.

For this reason, it is best practice to never specify any non-empty constructors. In fact, the easiest thing to do is to specify none as you did here.

What if you need to pass information or data to a Fragment? Hang on, you’ll get the answer to that later!

Adding a Fragment

Here’s where you get to add a fragment using the simplest approach — adding it to the activity’s XML layout. This is also sometimes called Adding a Fragment Statically.

To do this, open activity_main.xml, select the Text tab and add the following inside of the root FrameLayout:

<fragment
  android:id="@+id/details_fragment"
  class="com.raywenderlich.android.eldogo.DogDetailsFragment"
  android:layout_width="match_parent"
  android:layout_height="match_parent"/>
Note: You could also use android:name="com.raywenderlich.android.eldogo.DogDetailsFragment" instead of class="com.raywenderlich.android.eldogo.DogDetailsFragment".

In this step, you’re placing a <fragment> tag inside of the activity layout and specifying the type of fragment the class attribute should inflate. FragmentManager requires the view ID of the <fragment>. By including this in the XML, the FragmentManager knows to add this fragment to the activity automatically.

Build and run. You will see the fragment:

Detail fragment screenshot

Adding a Fragment Dynamically

First, open activity_main.xml again and remove the <fragment> you inserted. Embrace the Zen of deleting code! You’ll replace it with the list of dog breeds.

Open DogListFragment.kt, which has all the list code. You can see that the DogListFragment has no explicit constructors and a newInstance().

The list code in DogListFragment depends on some resources. You have to ensure that the fragment has a valid reference to a Context for accessing those resources. That’s where onAttach() comes into play.

In DogListFragment.kt and add these imports directly below the existing imports:

import android.os.Bundle
import android.support.v7.widget.GridLayoutManager

The GridLayoutManager helps in positioning items in the breed list. The other import is for standard fragment overrides.

Inside of DogListFragment.kt, add the following method above the definition of the DogListAdapter:

override fun onAttach(context: Context?) {
  super.onAttach(context)

  if (context != null) {
    // Get dog names and descriptions.
    val resources = context.resources
    names = resources.getStringArray(R.array.names)
    descriptions = resources.getStringArray(R.array.descriptions)
    urls = resources.getStringArray(R.array.urls)

    // Get dog images.
    val typedArray = resources.obtainTypedArray(R.array.images)
    val imageCount = names.size
    imageResIds = IntArray(imageCount)
    for (i in 0 until imageCount) {
      imageResIds[i] = typedArray.getResourceId(i, 0)
    }
    typedArray.recycle()
  }
}

onAttach() contains code that accesses the resources such as breed names and descriptions you need via the Context to which the fragment is attached. Because the code is in onAttach(), you can rest assured that the fragment has a valid Context.

Add this method right after the onAttach():

override fun onCreateView(inflater: LayoutInflater, 
                          container: ViewGroup?, 
                          savedInstanceState: Bundle?): View? {
  val view: View = inflater.inflate(R.layout.fragment_dog_list, container,
    false)
  val activity = activity as Context
  val recyclerView = view.findViewById<RecyclerView>(R.id.recycler_view)
  recyclerView.layoutManager = GridLayoutManager(activity, 2)
  recyclerView.adapter = DogListAdapter(activity)
  return view
}

In onCreateView(), you inflate the view hierarchy of DogListFragment, which contains a RecyclerView, and perform some standard RecyclerView setup.

If you have to inspect a fragment’s view, onCreateView() is a good place to start because it generates the view.

Next open MainActivity.kt and add the following to the bottom of onCreate():

// 1
if (savedInstanceState == null) {
  // 2
  supportFragmentManager
          // 3
          .beginTransaction()
          // 4
          .add(R.id.root_layout, DogListFragment.newInstance(), "dogList")
          // 5
          .commit()
}

At this point, to get DogListFragment into MainActivity. You ask your new friend, FragmentManager, to add it.

Here you:

  1. Check that the savedInstanceState is null. This is a way to see if it’s an initial open of the screen.
  2. Grab the FragmentManager by referencing supportFragmentManager as opposed to fragmentManager, since you are using support fragments.
  3. Ask that FragmentManager to start a new transaction by calling beginTransaction() — you probably figured that out yourself.
  4. Specify the add operation that you want by calling add and passing in:
    • The view ID of a container for the fragment’s view hierarchy in the activity’s layout. If you take a look at activity_main.xml, you’ll find @+id/root_layout.
    • The fragment instance you want to add.
    • A string that acts as a tag/identifier for the fragment instance. This allows the FragmentManager to later retrieve the fragment for you.
  5. Finally, ask the FragmentManager to execute the transaction by calling commit().

In the code above, an if block contains the code that displays the fragment and checks that the activity doesn’t have saved state. When an activity saves, all of its active fragments are also saved. If you don’t perform this check, this could happen:

android fragments too many

And you may feel like this:

Activity why you dont have just one fragment

The lesson: Always keep in mind how the saved state affects your fragments.

And with that, you’ve added the fragment!

Build, run and you’ll see a dog list once the app launches:

Dog list screen

FragmentManager helped achieve this awesomeness through FragmentTransactions, which are basically fragment operations such as add and remove.