Passing Data Between Two Fragments

You have passed data between two activities using intent extras, passed data between a fragment and an activity using a callbacks interface, and passed data from an activity to a fragment using fragment arguments. Now you need to pass data between two fragments that are hosted by the same activity – CrimeFragment and DatePickerFragment (Figure 13.4).

Figure 13.4  Conversation between CrimeFragment and DatePickerFragment

Conversation between CrimeFragment and DatePickerFragment

To get the Crime’s date to DatePickerFragment, you are going to write a newInstance(Date) function and make the Date an argument on the fragment.

To get the new date back to the CrimeFragment so that it can update the model layer and its own view, you will declare a callbacks interface function in DatePickerFragment that accepts the new date parameter, as shown in Figure 13.5.

Figure 13.5  Sequence of events between CrimeFragment and DatePickerFragment

Sequence of events between CrimeFragment and DatePickerFragment

Passing data to DatePickerFragment

To get data into your DatePickerFragment, you are going to stash the date in DatePickerFragment’s arguments bundle, where the DatePickerFragment can access it.

Creating and setting fragment arguments is typically done in a newInstance(…) function, as you saw in Chapter 12. In DatePickerFragment.kt, add a newInstance(Date) function in a companion object.

Listing 13.3  Adding a newInstance(Date) function (DatePickerFragment.kt)

private const val ARG_DATE = "date"

class DatePickerFragment : DialogFragment() {

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        ...
    }

    companion object {
        fun newInstance(date: Date): DatePickerFragment {
            val args = Bundle().apply {
                putSerializable(ARG_DATE, date)
            }

            return DatePickerFragment().apply {
                arguments = args
            }
        }
    }
}

In CrimeFragment, remove the call to the DatePickerFragment constructor and replace it with a call to DatePickerFragment.newInstance(Date).

Listing 13.4  Adding a call to newInstance(…) (CrimeFragment.kt)

override fun onStart() {
    ...
    dateButton.setOnClickListener {
        DatePickerFragment().apply {
        DatePickerFragment.newInstance(crime.date).apply {
            show([email protected](), DIALOG_DATE)
        }
    }
}

DatePickerFragment needs to initialize the DatePickerDialog using the information held in the Date. However, initializing the DatePickerDialog requires Ints for the month, day, and year. Date is more of a timestamp and cannot provide Ints like this directly.

To get the Ints you need, you provide the Date to the Calendar object. Then you can retrieve the required information from the Calendar.

In onCreateDialog(Bundle?), get the Date from the arguments and use it and the Calendar to initialize the DatePickerDialog.

Listing 13.5  Extracting the date and initializing DatePickerDialog (DatePickerFragment.kt)

class DatePickerFragment : DialogFragment() {

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        val date = arguments?.getSerializable(ARG_DATE) as Date
        val calendar = Calendar.getInstance()
        calendar.time = date
        val initialYear = calendar.get(Calendar.YEAR)
        val initialMonth = calendar.get(Calendar.MONTH)
        val initialDate = calendar.get(Calendar.DAY_OF_MONTH)

        return DatePickerDialog(
            requireContext(),
            null,
            initialYear,
            initialMonth,
            initialDate
        )
    }
    ...
}

Now CrimeFragment is successfully telling DatePickerFragment what date to show. You can run CriminalIntent and make sure that everything works as before.

Returning data to CrimeFragment

To have CrimeFragment receive the date back from DatePickerFragment, you need a way to keep track of the relationship between the two fragments.

With activities, you call startActivityForResult(…), and the ActivityManager keeps track of the parent-child activity relationship. When the child activity dies, the ActivityManager knows which activity should receive the result.

Setting a target fragment

You can create a similar connection by making CrimeFragment the target fragment of DatePickerFragment. This connection is automatically re-established after both CrimeFragment and DatePickerFragment are destroyed and re-created by the OS. To create this relationship, you call the following Fragment function:

    setTargetFragment(fragment: Fragment, requestCode: Int)

This function accepts the fragment that will be the target and a request code just like the one you send in startActivityForResult(…).

The FragmentManager keeps track of the target fragment and request code. You can retrieve them by accessing the targetFragment and targetRequestCode properties on the fragment that has set the target.

In CrimeFragment.kt, create a constant for the request code and then make CrimeFragment the target fragment of the DatePickerFragment instance.

Listing 13.6  Setting a target fragment (CrimeFragment.kt)

private const val DIALOG_DATE = "DialogDate"
private const val REQUEST_DATE = 0

class CrimeFragment : Fragment() {
    ...
    override fun onStart() {
        ...
        dateButton.setOnClickListener {
            DatePickerFragment.newInstance(crime.date).apply {
                setTargetFragment([email protected], REQUEST_DATE)
                show([email protected](), DIALOG_DATE)
            }
        }
    }
    ...
}

Sending data to the target fragment

Now that you have a connection between CrimeFragment and DatePickerFragment, you need to send the date back to CrimeFragment. You are going to create a callbacks interface in DatePickerFragment that CrimeFragment will implement.

In DatePickerFragment, create a callbacks interface with a single function called onDateSelected().

Listing 13.7  Creating a callbacks interface (DatePickerFragment.kt)

class DatePickerFragment : DialogFragment() {

    interface Callbacks {
        fun onDateSelected(date: Date)
    }

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        ...
    }
    ...
}

Next, implement the Callbacks interface in CrimeFragment. In onDateSelected(), set the date on the crime property and update the UI.

Listing 13.8  Implementing the callbacks interface (CrimeFragment.kt)

class CrimeFragment : Fragment(), DatePickerFragment.Callbacks {
    ...
    override fun onStop() {
        ...
    }

    override fun onDateSelected(date: Date) {
        crime.date = date
        updateUI()
    }
    ...
}

Now that CrimeFragment can respond to new dates, DatePickerFragment needs to send the new date when the user selects one. In DatePickerFragment, add a listener to the DatePickerDialog that sends the date back to CrimeFragment (Listing 13.9).

Listing 13.9  Sending back the date (DatePickerFragment.kt)

class DatePickerFragment : DialogFragment() {
    ...
    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        val dateListener = DatePickerDialog.OnDateSetListener {
                _: DatePicker, year: Int, month: Int, day: Int ->

            val resultDate : Date = GregorianCalendar(year, month, day).time

            targetFragment?.let { fragment ->
                (fragment as Callbacks).onDateSelected(resultDate)
            }
        }

        val date = arguments?.getSerializable(ARG_DATE) as Date
        ...
        return DatePickerDialog(
            requireContext(),
            null,
            dateListener,
            initialYear,
            initialMonth,
            initialDate
        )
    }
    ...
}

The OnDateSetListener is used to receive the date the user selects. The first parameter is for the DatePicker the result is coming from. Since you are not using that parameter in this case, you name it _. This is a Kotlin convention to denote parameters that are unused.

The selected date is provided in year, month, and day format, but you need a Date to send back to CrimeFragment. You pass these values to the GregorianCalendar and access the time property to get a Date object.

Once you have the date, it needs to be sent back to CrimeFragment. The targetFragment property stores the fragment instance that started your DatePickerFragment. Since it is nullable, you wrap it in a safe-call let block. You then cast the fragment instance to your Callbacks interface and call the onDateSelected() function, passing in your new date.

Now the circle is complete. The dates must flow. He who controls the dates controls time itself. Run CriminalIntent to ensure that you can, in fact, control the dates. Change the date of a Crime and confirm that the new date appears in CrimeFragment’s view. Then return to the list of crimes and check the Crime’s date to ensure that the model layer was updated.

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

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