DatePickerFragment を setFragmentResultListener を使って実装する

以前は setTargetFragment を使った実装をどこかのサンプルを元に作っていたのですが、久し振りにアプリを Kotlin で実装し直してたら deprecated になっていたので、またしても先人のサンプルを参考に作り直してみました。

数年前の実装(Java)

DatePickerDialog をそのまま使うのは非推奨ということで、DialogFragment と合わせて使うようにしていたとものと思います。 こうしないと、画面が回転した時に日付選択のダイアログが閉じてしまうんですよね。
import android.app.DatePickerDialog;
import android.app.Dialog;
import android.os.Bundle;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import android.widget.DatePicker;

public class DatePickerFragment extends DialogFragment
    implements DatePickerDialog.OnDateSetListener {

    public static DatePickerFragment
        newInstance(DatePickerFragment.OnDateSetListener targetFragment,
                    int dataId, int year, int month, int day)
    {
        if (targetFragment != null && !(targetFragment instanceof Fragment)) {
            throw new RuntimeException("targetFragment is not Fragment");
        }
        Bundle bundle = new Bundle();
        bundle.putInt("dataId", dataId);
        bundle.putInt("year", year);
        bundle.putInt("month", month);
        bundle.putInt("day", day);
        DatePickerFragment picker = new DatePickerFragment();
        picker.setArguments(bundle);
        picker.setTargetFragment((Fragment)targetFragment, 0);
        return picker;
    }

    public int getDataId() {
        return getArguments().getInt("dataId", -1);
    }

    public int getYear() {
        return getArguments().getInt("year", 0);
    }

    public int getMonth() {
        return getArguments().getInt("month", 0);
    }

    public int getDay() {
        return getArguments().getInt("day", 0);
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        int year = getYear();
        int month = getMonth();
        int day = getDay();
        return new DatePickerDialog(getActivity(), this, year, month, day);
    }

    public void onDateSet(DatePicker view, int year, int month, int day) {
        OnDateSetListener listener = (OnDateSetListener)getTargetFragment();
        listener.onDateSet(getDataId(), view, year, month, day);
    }

    static public interface OnDateSetListener {
        void onDateSet(int dataId, DatePicker view, int year, int month, int day);
    }
}

今回の実装(Kotlin)

単純に Kotlin に焼き直そうとしたのですが、setTargetFragment が deprecated になってしまってました。(いつの話よ、というツッコミが) いつもお世話になっている Qiita に良さげな実装提案が1年も前にアップされていたので、参考にさせていただきました。
import android.app.DatePickerDialog
import android.app.Dialog
import android.os.Bundle
import android.widget.DatePicker
import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.fragment.app.setFragmentResult
import org.threeten.bp.LocalDate
import java.util.*

class DatePickerFragment : DialogFragment(), DatePickerDialog.OnDateSetListener {

    companion object {
        private const val REQUEST_KEY = "request_key_date_picker_fragment"
        private const val RESULT_KEY_SET_DATE = "result_key_date_picker_set_date"

        fun show(
            target: Fragment,
            timeInMillis: Long,
            onSetDate: ((year: Int, month: Int, dayOfMonth: Int) -> Unit)? = null
        ) {
            val bundle = Bundle()
            val cal = Calendar.getInstance()
            cal.timeInMillis = timeInMillis
            bundle.putInt("year", cal[Calendar.YEAR])
            bundle.putInt("month", cal[Calendar.MONTH])
            bundle.putInt("dayOfMonth", cal[Calendar.DAY_OF_MONTH])
            newInstance().run {
                arguments = bundle
                target
                    .childFragmentManager
                    .setFragmentResultListener(
                        REQUEST_KEY,
                        target.viewLifecycleOwner
                    ) { requestKey, bundle ->
                        if (requestKey != REQUEST_KEY) return@setFragmentResultListener
                        when {
                            bundle.containsKey(RESULT_KEY_SET_DATE) -> {
                                val picked = bundle[RESULT_KEY_SET_DATE] as LocalDate
                                onSetDate?.invoke(
                                    picked.year,
                                    picked.monthValue - 1,
                                    picked.dayOfMonth
                                )
                            }
                        }
                    }
                show(target.childFragmentManager, "DatePickerFragment")
            }
        }

        private fun newInstance() = DatePickerFragment()
    }

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        val year = arguments?.getInt("year", 0) ?: 0
        val month = arguments?.getInt("month", 0) ?: 0
        val dayOfMonth = arguments?.getInt("dayOfMonth", 1) ?: 1
        return DatePickerDialog(requireContext(), this, year, month, dayOfMonth)
    }

    override fun onDateSet(picker: DatePicker?, year: Int, month: Int, dayOfMonth: Int) {
        setFragmentResult(
            REQUEST_KEY,
            bundleOf(RESULT_KEY_SET_DATE to LocalDate.of(year, month + 1, dayOfMonth))
        )
    }
}
アプリから使うとこんな感じです。
        binding.date.setOnClickListener {
            DatePickerFragment.show(
                target = this,
                timeInMillis = viewModel.date.value?.time ?: 0,
                onSetDate = { year, month, dayOfMonth ->
                    val cal = Calendar.getInstance()
                    cal.time = viewModel.date.value ?: Date()
                    cal[Calendar.YEAR] = year
                    cal[Calendar.MONTH] = month
                    cal[Calendar.DAY_OF_MONTH] = dayOfMonth
                    viewModel.date.value = cal.time
                }
            )
        }
最後にスクショです。見た目は元のままですけど、Android Studio でワーニングが出なくなるのがいいですよね。
ポートレイトレイアウト
ランドスケープレイアウト

参考リンク

コメントを残す

メールアドレスが公開されることはありません。