Проблема с возвратом даты из фрагмента DatePickerDialog при использовании контроллера навигации

Я пытаюсь вернуть дату, выбранную из моего DatePickerDialog. Я знаю, что здесь, на SO, есть несколько вопросов по этому поводу, и я уверен, что проверил их в своем коде, и я все еще не могу вернуть дату в свой фрагмент, который вызывает DatePickerDialog.

Я использую интерфейс навигации для отображения диалогового окна, может ли это быть причиной проблемы?

Вот мой DatePickerDialog:

class DatePickerFragment : DialogFragment(), DatePickerDialog.OnDateSetListener {

    private var onDateSetListener: DatePickerDialog.OnDateSetListener? = null

    fun setListeningActivity(listener: DatePickerDialog.OnDateSetListener) {
        onDateSetListener = listener
    }

    override fun onDateSet(view: DatePicker?, year: Int, month: Int, dayOfMonth: Int) {
        onDateSetListener?.onDateSet(view,year, month, dayOfMonth)
    }

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        // Use the current date as the default date in the picker
        val c = Calendar.getInstance()
        val year = c.get(Calendar.YEAR)
        val month = c.get(Calendar.MONTH)
        val day = c.get(Calendar.DAY_OF_MONTH)

        // Create a new instance of DatePickerDialog and return it
        return DatePickerDialog(context, this, year, month, day)
    }
}

Соответствующий код из фрагмента, вызывающего Datepicker:

private fun showDatePicker(hasFocus: Boolean, view: View) {
    Log.i(FRAGMENT_NAME, "Has focus $hasFocus")
    val fragment = DatePickerFragment()
    fragment.setListeningActivity(this)
    if (hasFocus) {
        Navigation.findNavController(view).navigate(R.id.datePickerFragment)
    }
}

override fun onDateSet(view: DatePicker, year: Int, month: Int, day: Int) {
    Log.i(FRAGMENT_NAME, "Date received: $year-$month-$day")
    fragmentView.dateOfBirthField.editText?.setText("$year-$month-$day")
}

Вот как я вызываю метод showDatePicker:

fragmentView.dateOfBirthField.editText?.setOnFocusChangeListener { _, hasFocus -> showDatePicker(hasFocus, fragmentView) }

Вопрос: Как вернуть дату во фрагмент, вызывающий DatePickerDialog. Я не хочу, чтобы DatePickerDialog был внутренним классом для класса, который его вызывает.

Я бы предложил использовать livedata с ViewModel.

Moinkhan 03.06.2019 10:48

Самым простым решением было бы использовать EventBus

cesarmarch 04.06.2019 20:15
3
2
1 309
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Можно либо установить цель Fragment:

val fragment = DatePickerFragment()
fragment.setTargetFragment(this, requestCode)

или пусть он доставит результат Activity:

val fragment = DatePickerFragment()
fragment.setListeningActivity(this)

это должно произойти до показа DialogFragment; так что DialogFragment уже будет знать, куда он должен доставить свой результат. Для любого пункта назначения метод onDateSet() должен возвращать Intent с дополнительными Bundle... и onActivityResult() должен обработать этот результат и соответствующим образом обновить графический интерфейс. Вот как работает общение между Activity и Fragment, и это также относится к DialogFragment. Метод onActivityResult() может обрабатывать обратные вызовы как от Activity, так и от Fragment.

В Java это было бы примерно так — для возврата результата в DialogFragment, в методе override fun onDateSet(view: DatePicker, year: Int, month: Int, day: Int):

/* converting the selection to long integer */
GregorianCalendar date = new java.util.GregorianCalendar();
date.set(GregorianCalendar.YEAR, year);
date.set(GregorianCalendar.MONTH, month);
date.set(GregorianCalendar.DAY_OF_MONTH, day);
Log.d(LOG_TAG, "confirmed " + date.getTime());
long dueDate = date.getTime().getTime();

if (getTargetFragment() != null) {

    /* stuffing the result value into a Bundle */
    Intent intent = this.getActivity().getIntent();
    Bundle extras = new Bundle();
    extras.putLong(ArgumentKeys.ARGUMENT_SELECT_DUE_DATE, dueDate);
    intent.putExtras(extras);

    /* this delivers the result */
    getTargetFragment().onActivityResult(getTargetRequestCode(), Activity.RESULT_OK, intent);

    /* close dialog */
    this.dismiss();

} else {
    Log.w(LOG_TAG, ".setTargetFragment() had not been called.");
}

или аналогично для возврата к Activity:

if (this.dialog.getOwnerActivity() != null) {
    ...
}

и onActivityResult() из Activity или Fragment, которые открыли DialogFragment:

@Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
    Bundle extras = intent.getExtras();
    if (resultCode == Activity.RESULT_OK) {
        switch (requestCode) {
            case RequestCodes.REQUESTCODE_TASK_DUE_DATE: {
                if (extras != null) {
                    long dueDate = extras.getLong(ArgumentKeys.ARGUMENT_SELECT_DUE_DATE, -1);

                        /* set the value in here */
                    }
                }
            }
            ...
    }
}

возврат результата Fragment работает почти так же, как возврат результата Activity.

Ответ принят как подходящий

Лучший способ передачи данных фрагмента — livedata с viewmodel, когда вы используете компонент архитектуры навигации.

Шаг 1 Создайте ViewModel, в котором будут храниться ваши selectedDate. Создайте экземпляр LiveData, чтобы вы могли наблюдать за изменениями.

class SharedViewModel: ViewModel() {
    val selectedDate: MutableLiveData<String> = MutableLiveData()
}

Шаг 2 Получить экземпляр SharedViewModel и обновить переменную selectedDate, чтобы уведомить об этом наблюдателей. (В DatePickerFragment)

override fun onDateSet(view: DatePicker, year: Int, month: Int, day: Int) {
    val viewModel = ViewModelProviders.of(activity!!)[SharedViewModel::class.java]
    viewModel.selectedDate.value = "$year $month $day"
}

Шаг 3 Получите экземпляр SharedViewModel, где вы хотите наблюдать за изменениями. (В нашем случае откуда вызывается datePickerFragment.)

override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)

    ViewModelProviders.of(activity!!)[SharedViewModel::class.java]
            .selectedDate
            .observe(activity!!, Observer { dateOfBirthField.editText.setText(it) })
}

Если вы используете androidx артефакт. Тогда вам может понадобиться следующая зависимость.

implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0-alpha01'

Эй, чувак, извини, что не вернулся к этому раньше. Это сработало отлично, спасибо за подробное объяснение и пример кода. Просто быстрый вопрос, почему стандартный слушатель не работает при использовании навигационного контроллера?

DrkStr 16.06.2019 04:32

Я не уверена. Но я думаю, это не сработало, потому что экземпляр фрагмента создается navcontroller. Итак, в этом случае у нас нет правильной ссылки в исходном фрагменте.

Moinkhan 16.06.2019 05:53

Да, извините за это. Могу ли я как-то отправить вам 50 баллов?

DrkStr 16.06.2019 05:58

Нет проблем, братан, у тебя есть решение, которое важнее.

Moinkhan 16.06.2019 06:01

Другие вопросы по теме