我使用DialogFragments的一些事情:从列表中选择项目,输入文本。
将值(即字符串或列表中的项)返回给调用活动/片段的最佳方法是什么?
目前,我正在使调用活动实现驳回监听器,并给予DialogFragment对活动的引用。然后Dialog在activity中调用ondismiss方法,activity从DialogFragment对象中抓取结果。非常混乱,它不能在配置更改(方向更改),因为DialogFragment失去了对活动的引用。
谢谢你的帮助。
我使用DialogFragments的一些事情:从列表中选择项目,输入文本。
将值(即字符串或列表中的项)返回给调用活动/片段的最佳方法是什么?
目前,我正在使调用活动实现驳回监听器,并给予DialogFragment对活动的引用。然后Dialog在activity中调用ondismiss方法,activity从DialogFragment对象中抓取结果。非常混乱,它不能在配置更改(方向更改),因为DialogFragment失去了对活动的引用。
谢谢你的帮助。
当前回答
对于还在阅读这篇文章的人:setTargetFragment()已被弃用。现在建议像这样使用FragmentResultListener API:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setFragmentResultListener("requestKey") { key, bundle ->
val result = bundle.getString("resultKey")
// Do something with the result...
}
...
// Somewhere show your dialog
MyDialogFragment.newInstance().show(parentFragmentManager, "tag")
}
然后在你的MyDialogFragment中设置结果:
button.setOnClickListener{
val result = "some string"
setFragmentResult("requestKey", bundleOf("resultKey" to result))
dismiss()
}
其他回答
在Kotlin
// My DialogFragment
class FiltroDialogFragment : DialogFragment(), View.OnClickListener {
var listener: InterfaceCommunicator? = null
override fun onAttach(context: Context?) {
super.onAttach(context)
listener = context as InterfaceCommunicator
}
interface InterfaceCommunicator {
fun sendRequest(value: String)
}
override fun onClick(v: View) {
when (v.id) {
R.id.buttonOk -> {
//You can change value
listener?.sendRequest('send data')
dismiss()
}
}
}
}
//我的活动
class MyActivity: AppCompatActivity(),FiltroDialogFragment.InterfaceCommunicator {
override fun sendRequest(value: String) {
// :)
Toast.makeText(this, value, Toast.LENGTH_LONG).show()
}
}
我希望它服务,如果你可以改进,请编辑它。 我的英语不太好
只是把它作为一个选项(因为还没有人提到它)——你可以使用像Otto这样的事件总线。 所以在对话中你要这样做:
bus.post(new AnswerAvailableEvent(42));
并让你的调用者(Activity或Fragment)订阅它:
@Subscribe public void answerAvailable(AnswerAvailableEvent event) {
// TODO: React to the event somehow!
}
如果你想发送参数并从第二个片段接收结果,你可以使用fragment。setargarguments来完成这个任务
static class FirstFragment extends Fragment {
final Handler mUIHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 101: // receive the result from SecondFragment
Object result = msg.obj;
// do something according to the result
break;
}
};
};
void onStartSecondFragments() {
Message msg = Message.obtain(mUIHandler, 101, 102, 103, new Object()); // replace Object with a Parcelable if you want to across Save/Restore
// instance
putParcelable(new SecondFragment(), msg).show(getFragmentManager().beginTransaction(), null);
}
}
static class SecondFragment extends DialogFragment {
Message mMsg; // arguments from the caller/FirstFragment
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onViewCreated(view, savedInstanceState);
mMsg = getParcelable(this);
}
void onClickOK() {
mMsg.obj = new Object(); // send the result to the caller/FirstFragment
mMsg.sendToTarget();
}
}
static <T extends Fragment> T putParcelable(T f, Parcelable arg) {
if (f.getArguments() == null) {
f.setArguments(new Bundle());
}
f.getArguments().putParcelable("extra_args", arg);
return f;
}
static <T extends Parcelable> T getParcelable(Fragment f) {
return f.getArguments().getParcelable("extra_args");
}
我很惊讶地看到,没有人建议使用本地广播进行DialogFragment到Activity的通信!我发现它比其他建议更简单、更清晰。本质上,您为您的Activity注册侦听广播,并从您的DialogFragment实例发送本地广播。简单。有关如何设置它的一步一步的指南,请参见这里。
使用这个AppDialog类既可以将数据传递到DialogFragment,也可以从中获取结果。
详细解释:
Premise - Fragments get destroyed and recreated on config changes. View models hang around. When using a Dialog, it is recommended to wrap it in DialogFragment so that when the user rotates device and changes orientation the Dialog will not unexpectedly disappear (the DialogFragment will re-create it and re-display it). Limitation (hence this question) - The way the DialogFragment works is it takes a class that it will need to re-instantiate on configuration changes - that means one can't have constructor parameters to the subclass to pass parameters, and typically one needs to make custom callbacks through a view model to pass back result of dialog. That typically means a new subclass for every dialog. The solution - To help with all this, this custom AppDialog fragment comes to the rescue - the parameters are stored in-memory (similar to view model, you can think of it as a tiny custom view model that holds T in memory and uses it to re-create the dialog on config changes) until the dialog fragment is dismissed. The proper way to call back would be through a view model. If the fragment that shows the AppDialog, then you probably already have a view model and you can reference it from the lambda used to create the dialog - that means additional strong reference to the view model until the dialog fragment is dismissed. Example - see the examples where a simple Dialog is refactored to use this AppDialog utility class to both receive a parameter and do a callback to viewModel to notify of result.
helper类:
class AppDialog<T>: DialogFragment() {
companion object {
fun<T> buildDialog(params: T? = null, builder: AppDialogLambda<T>): AppDialog<T> {
// Setup arguments
val args = Bundle()
args.putInt("key", pushDialogArgs(params, builder))
// Instantiate
val fragment = AppDialog<T>()
fragment.arguments = args
return fragment
}
// --------------------
// Dialog Arguments
private var lastKey: Int = 0
private val dialogArgs = mutableMapOf<Int, Pair<Any?, AppDialogLambda<*>>>()
private fun pushDialogArgs(params: Any?, builder: AppDialogLambda<*>): Int {
dialogArgs[lastKey] = params to builder
return lastKey++
}
private fun getDialogArgs(key: Int): Pair<Any?, AppDialogLambda<*>> {
return dialogArgs[key]!!
}
private fun deleteDialogArgs(key: Int) {
dialogArgs.remove(key)
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
// Get arguments
val argKey = requireArguments().getInt("key")
val (params, builder) = getDialogArgs(argKey)
// We are getting back our arguments we passed AppDialog.buildDialog and
// the type is guaranteed to be the same. Silence this warning
@Suppress("UNCHECKED_CAST")
return (builder as AppDialogLambda<T>)(this, params as T?)
}
override fun onDismiss(dialog: DialogInterface) {
super.onDismiss(dialog)
val argKey = requireArguments().getInt("key")
deleteDialogArgs(argKey)
}
}
示例用法(之后):
val info = mapOf("message" to "${error.description}\n\nPlease check your Internet connection and try again.")
AppDialog.buildDialog(info) { fragment, params ->
fragment.isCancelable = false // since we are in a DialogFragment
AlertDialog.Builder(fragment.context)
.setTitle("Terms Of Service Failed To Load")
.setMessage(params!!["message"])
.setPositiveButton("Retry") { _, _ ->
// Update the view model instead of calling UserTOSFragment directly
// as the fragment may be destroyed and recreated
// on configuration changes. The viewModel will stay alive.
viewModel.onTermsOfServiceReload()
}
.setNegativeButton("Cancel") { _, _ ->
viewModel.onTermsOfServiceDeclined()
fragment.findNavController().popBackStack()
}.create()
}.show(parentFragmentManager, "TOS Failed Dialog")
用法示例(前): 如果不使用DialogFragment(为了说明目的,不要这样做,这是不好的做法,因为对话框将在配置更改时被销毁),则UserTOSFragment中的代码。kt -用于在重试时直接调用UserTOSFragment.loadContent()的注释代码。在上面的例子中,这必须重写为调用viewModel.onTermsOfServiceDeclined():
AlertDialog.Builder(context)
.setTitle("Terms Of Service Failed To Load")
.setMessage("${error.description}\n\nPlease check your Internet connection and try again.")
.setPositiveButton("Retry") { _, _ ->
loadContent()
}
.setCancelable(false)
.setNegativeButton("Cancel") { _, _ ->
viewModel.onTermsOfServiceDeclined()
findNavController().popBackStack()
}
.show()