Я пытаюсь вернуть дату, выбранную из моего 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 был внутренним классом для класса, который его вызывает.
Самым простым решением было бы использовать EventBus
Можно либо установить цель 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'
Эй, чувак, извини, что не вернулся к этому раньше. Это сработало отлично, спасибо за подробное объяснение и пример кода. Просто быстрый вопрос, почему стандартный слушатель не работает при использовании навигационного контроллера?
Я не уверена. Но я думаю, это не сработало, потому что экземпляр фрагмента создается navcontroller. Итак, в этом случае у нас нет правильной ссылки в исходном фрагменте.
Да, извините за это. Могу ли я как-то отправить вам 50 баллов?
Нет проблем, братан, у тебя есть решение, которое важнее.
Я бы предложил использовать
livedata
сViewModel
.