使用导航控制器时无法从 DatePickerDialog 片段返回日期

Trouble returning date from DatePickerDialog fragment when using Navigation Controller

我正在尝试 return 从我的 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)
    }
}

调用日期选择器的片段中的相关代码:

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 作为调用它的 class 的内部 class。

可以设置一个目标 Fragment:

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

或者让它把结果传递给 Activity:

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

这应该在显示 DialogFragment 之前发生;这样 DialogFragment 就会知道它必须将结果传递到哪里。对于任何一个交付目的地,方法 onDateSet() 需要 return 一个带有额外 BundleIntent... 并且 onActivityResult() 需要处理该结果并更新 GUI因此。这就是 ActivityFragment 之间的通信方式——这也适用于 DialogFragment。方法 onActivityResult() 可以处理来自 ActivityFragment.

的回调

在 Java 中类似于这样 - 对于 return 在 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.");
}

或类似地 returning 到 Activity:

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

ActivityFragmentonActivityResult()打开了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 */
                    }
                }
            }
            ...
    }
}

return Fragment 结果与 return Activity 结果几乎相同。

片段数据传递的最佳方式是 livedataviewmodel,当您使用导航架构组件时。

第 1 步 创建 ViewModel 来容纳你的 selectedDate。创建 LiveData 的实例,以便您可以观察更改。

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

Step-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"
}

Step-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'