Fragment Arguments

CrimeListFragment now notifies its hosting activity (MainActivity) when a crime is selected and passes along the ID of the selected crime.

That is all fine and dandy. But what you really need is a way to pass the selected crime ID from MainActivity to CrimeFragment. This way, CrimeFragment can pull data for that crime from the database and populate the UI with that data.

Fragment arguments offer a solution to this problem – they allow you to stash pieces of data someplace that belongs to the fragment. The someplace that belongs to a fragment is known as its arguments bundle. The fragment can retrieve data from the arguments bundle without relying on its parent activity or some other outside source.

Fragment arguments help you keep your fragment encapsulated. A well-encapsulated fragment is a reusable building block that can easily be hosted in any activity.

To create fragment arguments, you first create a Bundle object. This bundle contains key-value pairs that work just like the intent extras of an Activity. Each pair is known as an argument. Next, you use type-specific put functions of Bundle (similar to those of Intent) to add arguments to the bundle:

    val args = Bundle().apply {
      putSerializable(ARG_MY_OBJECT, myObject)
      putInt(ARG_MY_INT, myInt)
      putCharSequence(ARG_MY_STRING, myString)
    }

Every fragment instance can have a fragment arguments Bundle object attached to it.

Attaching arguments to a fragment

To attach the arguments bundle to a fragment, you call Fragment.setArguments(Bundle). Attaching arguments to a fragment must be done after the fragment is created but before it is added to an activity.

To accomplish this, Android programmers follow a convention of adding a companion object that contains the newInstance(…) function to the Fragment class. This function creates the fragment instance and bundles up and sets its arguments.

When the hosting activity needs an instance of that fragment, you have it call the newInstance(…) function rather than calling the constructor directly. The activity can pass in any required parameters to newInstance(…) that the fragment needs to create its arguments.

In CrimeFragment, write a newInstance(UUID) function that accepts a UUID, creates an arguments bundle, creates a fragment instance, and then attaches the arguments to the fragment.

Listing 12.6  Writing a newInstance(UUID) function (CrimeFragment.kt)

private const val ARG_CRIME_ID = "crime_id"

class CrimeFragment : Fragment() {
    ...
    override fun onStart() {
        ...
    }

    companion object {

        fun newInstance(crimeId: UUID): CrimeFragment {
            val args = Bundle().apply {
                putSerializable(ARG_CRIME_ID, crimeId)
            }
            return CrimeFragment().apply {
                arguments = args
            }
        }
    }
}

Update MainActivity to call CrimeFragment.newInstance(UUID) when it needs to create a CrimeFragment. Pass along the UUID that was received in MainActivity.onCrimeSelected(UUID).

Listing 12.7  Using CrimeFragment.newInstance(UUID) (MainActivity.kt)

class MainActivity : AppCompatActivity(),
    CrimeListFragment.Callbacks {
    ...
    override fun onCrimeSelected(crimeId: UUID) {
        val fragment = CrimeFragment()
        val fragment = CrimeFragment.newInstance(crimeId)
        supportFragmentManager
            .beginTransaction()
            .replace(R.id.fragment_container, fragment)
            .addToBackStack(null)
            .commit()
    }
}

Notice that the need for independence does not go both ways. MainActivity has to know plenty about CrimeFragment, including that it has a newInstance(UUID) function. This is fine. Hosting activities should know the specifics of how to host their fragments, but fragments should not have to know specifics about their activities. At least, not if you want to maintain the flexibility of independent fragments.

Retrieving arguments

To access a fragment’s arguments, reference the Fragment property named arguments. Then use one of the type-specific get functions of Bundle to pull individual values out of the arguments bundle.

Back in CrimeFragment.onCreate(…), retrieve the UUID from the fragment arguments. For now, log the ID so you can verify that the argument was attached as expected.

Listing 12.8  Getting crime ID from the arguments (CrimeFragment.kt)

private const val TAG = "CrimeFragment"
private const val ARG_CRIME_ID = "crime_id"

class CrimeFragment : Fragment() {
    ...
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        crime = Crime()
        val crimeId: UUID = arguments?.getSerializable(ARG_CRIME_ID) as UUID
        Log.d(TAG, "args bundle crime ID: $crimeId")
        // Eventually, load crime from database
    }
    ...
}

Run CriminalIntent. The app will behave the same, but you should feel all warm and fuzzy inside for passing the crime ID along to CrimeFragment while still maintaining CrimeFragment’s independence.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.223.107.149