从DialogFragment接收结果

255

我正在使用DialogFragments来完成一些事情:从列表中选择项目,输入文本等。

那么,将值(例如字符串或列表中的项目)返回给调用的activity/fragment的最佳方法是什么?

目前,我让调用的activity实现DismissListener,并将DialogFragment的引用传递给activity。然后,Dialog调用activity中的OnDimiss方法,并且activity从DialogFragment对象中获取结果。这种做法非常混乱,而且在配置更改(方向更改)时不起作用,因为DialogFragment会失去对activity的引用。

感谢任何帮助。


10
DialogFragments仍然只是片段。你的方法实际上是片段与主活动交流的推荐方式。http://developer.android.com/guide/topics/fundamentals/fragments.html#CommunicatingWithActivity - codinguser
1
谢谢你。我很接近了(就像你说的那样)。那个链接文档帮助我的部分是使用onAttach()并将活动转换为侦听器。 - James Cross
2
@codinguser, @Styx - "给DialogFragment一个对Activity的引用" - 这个细节有些冒险,因为ActivityDialogFragment都可能被重新创建。使用传递给onAttach(Activity activity)Activity是正确和推荐的方式。 - sstn
请检查我的答案:https://dev59.com/oLPma4cB1Zd3GeqPtJvD#67282822 - Amr
17个回答

3
在我的情况下,我需要将参数传递给目标碎片。但是我收到了"碎片已经活动"的异常。因此,我在我的DialogFragment中声明了一个Interface,由parentFragment实现。当parentFragment启动DialogFragment时,它将自身设置为TargetFragment。然后在DialogFragment中我调用:
 ((Interface)getTargetFragment()).onSomething(selectedListPosition);

1

不同的方法(2023年):

  • 使用导航图:只需为返回方向(从对话框片段到其父级)设置参数,并在目标位置接收它。

从对话框发送(Java):NavHostFragment.navigate(this, YourDialogName+Directions .action+---+To---(DefinedArgument)));

在父级中接收(Java):dialogResult = NameOfFragmentArgs.fromBundle(requireArguments()).get+DefinedArgument;

  • 使用导航图和ViewModel+LiveData(推荐)

对于这种方法,CommonsWare网站上有一个完整的示例。

  • 使用 Intent 和 Bundle

  • 使用自定义 UI 来创建对话框,这样你就可以直接访问片段内正面和负面按钮的 setOnClickListener 方法;

  • 实现你定义的接口,例如在目标片段/活动中的 setOnYes

  • 使用 FragmentManagersetFragmentResult(String, Bundle)(参见此帖子)


1
如果您想从第二个片段发送参数并接收结果,可以使用Fragment.setArguments来完成此任务。
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");
}

0

简而言之 - 使用这个AppDialog类既可以将数据传递到DialogFragment中,也可以从中获取结果。

详细解释:

  • 前提 - 片段在配置更改时被销毁并重新创建。视图模型会一直存在。当使用对话框时,建议将其包装在DialogFragment中,以便在用户旋转设备并更改方向时,对话框不会意外消失(DialogFragment将重新创建它并重新显示它)。
  • 限制(因此出现了这个问题)- DialogFragment的工作方式是它需要在配置更改时重新实例化一个类 - 这意味着不能有构造函数参数传递给子类,通常需要通过视图模型进行自定义回调来传递对话框结果。这通常意味着为每个对话框创建一个新的子类。
  • 解决方案 - 为了帮助解决所有这些问题,这个自定义AppDialog片段应运而生 - 参数存储在内存中(类似于视图模型,您可以将其视为在内存中保存T的小型自定义视图模型,并使用它在配置更改时重新创建对话框),直到对话框片段被关闭。正确的回调方式是通过视图模型。如果显示AppDialog的片段,则可能已经有一个视图模型,您可以从用于创建对话框的lambda中引用它 - 这意味着在对话框片段被关闭之前,视图模型有额外的强引用。
  • 示例 - 请参见示例,其中将简单对话框重构为使用此AppDialog实用程序类来接收参数并执行回调以通知viewModel结果。

辅助类:


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()

0
标记的答案已过时。请使用新的片段结果 API
但是,不要使用:
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    
    setFragmentResultListener("requestKey") { requestKey, bundle ->
        val result = bundle.getString("bundleKey")
    }
}

用于活动和标准根片段到根片段的通信的工作,使用:
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    
 childFragmentManager.setFragmentResultListener("requestKey",this/*lifecycleOwner*/) { requestKey, bundle ->
        val result = bundle.getString("bundleKey")
    }
}

显示dialogFragment的方式如下:
MyDialogFragment().show(childFragmentManager, "tag")

-2

在一个对话框片段中

class AbcDialogFragment(private val ondata: (data: String) -> Unit) :  DialogFragment() {}

从片段/活动中显示对话框的代码

val abcDialogFragment = AbcDialogFragment(ondata = {data->  })
                
abcDialogFragment.show(requireActivity().supportFragmentManager, "TAG")

在对话框片段中,当对话框片段关闭或任何点击监听器时,您可以调用onData。


-3

仅仅是为了提供其中一个选项(因为还没有人提到它)- 您可以使用像Otto这样的事件总线。

所以在对话框中,您可以执行以下操作:

bus.post(new AnswerAvailableEvent(42));

并让您的调用者(Activity或Fragment)进行订阅:

@Subscribe public void answerAvailable(AnswerAvailableEvent event) {
   // TODO: React to the event somehow!
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接