从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个回答

260

在显示对话框的地方使用myDialogFragment.setTargetFragment(this, MY_REQUEST_CODE),然后当对话框完成时,你可以从中调用getTargetFragment().onActivityResult(getTargetRequestCode(), ...),并在包含片段中实现onActivityResult()

看起来像是滥用onActivityResult(),特别是它根本不涉及活动。但我曾经看到过官方的 Google 人员推荐,并且甚至可能在 API 演示中看到过这种用法。我认为这就是添加g/setTargetFragment()的原因。


92
如果目标是一项活动,该怎么办? - Fernando Gallego
9
如果目标是Activity,我会声明一个带有方法“void onActivityResult2(int requestCode, int resultCode, Intent data)”的接口,并由一个Activity来实现它。在DialogFragment中,只需获取Activity并检查此接口,然后适当地调用它即可。 - Ruslan Yanchyshyn
4
这不是一个好的解决方案。在保存和恢复对话框片段状态后,它将不起作用。在这种情况下,LocalBroadcastManager 是最好的解决方案。 - Nik
4
@Nik,那不是真的。这是最好的解决方案。保存和还原状态时没有问题。如果你遇到了问题,那么你可能使用了错误的片段管理器。目标片段/调用者必须使用getChildFragmentManager()来显示对话框。 - The incredible Jan
4
setTargetFragment现已被弃用,但替代方法FragmentManager.setFragmentResultListener(在片段之间传递数据中描述)仍处于alpha版本。 - big_m
显示剩余6条评论

143

如您所见,这里有一种非常���单的方法来实现这一点。

在您的 DialogFragment 中添加一个接口监听器,例如:

public interface EditNameDialogListener {
    void onFinishEditDialog(String inputText);
}

然后,添加对该侦听器的引用:

private EditNameDialogListener listener;

这将用于“激活”监听方法,并检查父Activity / Fragment是否实现了此接口(见下文)。
在“调用”DialogFragment的Activity / FragmentActivity / Fragment中,只需实现此接口。
在您的DialogFragment中,您只需要在想要关闭DialogFragment并返回结果的位置添加以下内容:
listener.onFinishEditDialog(mEditText.getText().toString());
this.dismiss();

mEditText.getText().toString() 是将回传到调用 Activity 的内容。

请注意,如果您想返回其他内容,只需更改监听器所接受的参数即可。

最后,请检查父活动/片段是否实际上已实现该接口:

@Override
public void onAttach(Context context) {
    super.onAttach(context);
    // Verify that the host activity implements the callback interface
    try {
        // Instantiate the EditNameDialogListener so we can send events to the host
        listener = (EditNameDialogListener) context;
    } catch (ClassCastException e) {
        // The activity doesn't implement the interface, throw exception
        throw new ClassCastException(context.toString()
                + " must implement EditNameDialogListener");
    }
}

这种技术非常灵活,即使您还不想关闭对话框,也可以通过结果进行回调。

14
这在ActivityFragmentActivity中效果很好,但如果调用者是一个Fragment呢? - Brais Gabin
1
我不确定我完全理解你的意思。但是如果调用者是“Fragment”,它将起同样的作用。 - Assaf Gamliel
2
如果调用者是一个Fragment,那么你可以做一些事情:
  1. 将该片段作为引用传递(可能不是一个好主意,因为可能会导致内存泄漏)。
  2. 使用FragmentManager并调用findFragmentByIdfindFragmentByTag,它将获取在您的活动中存在的片段。 希望这有所帮助。祝你拥有美好的一天!
- Assaf Gamliel
5
这种方法的问题在于,片段不擅长保留对象,因为它们旨在被重新创建。例如,尝试更改方向时,操作系统将重新创建该片段,但侦听器实例将不再可用。 - Necronet
5
@LOG_TAG看一下@Timmmm的回答。setTargetFragment()getTargetFragment()很神奇。 - Brais Gabin
显示剩余9条评论

52

有一种更简单的方法可以从DialogFragment中获取结果。

首先,在您的Activity、Fragment或FragmentActivity中,您需要添加以下信息:

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    // Stuff to do, dependent on requestCode and resultCode
    if(requestCode == 1) { // 1 is an arbitrary number, can be any int
         // This is the return result of your DialogFragment
         if(resultCode == 1) { // 1 is an arbitrary number, can be any int
              // Now do what you need to do after the dialog dismisses.
         }
     }
}

requestCode就是你为所调用的DialogFragment设定的int类型标签,我马上就会展示它的使用方法。而resultCode则是你从DialogFragment中发送回来,告知当前等待结果的Activity、Fragment或FragmentActivity发生了什么事情的代码。

下一步需要添加的代码是对DialogFragment的调用。这里提供一个例子:

DialogFragment dialogFrag = new MyDialogFragment();
// This is the requestCode that you are sending.
dialogFrag.setTargetFragment(this, 1);     
// This is the tag, "dialog" being sent.
dialogFrag.show(getFragmentManager(), "dialog");

使用这三行代码声明DialogFragment,设置一个requestCode(该requestCode将在对话框关闭后调用onActivityResult(...)),然后显示对话框。就是这么简单。

现在,在DialogFragment中,您只需要在dismiss()之前添加一行代码,以便向onActivityResult()发送一个resultCode。

getTargetFragment().onActivityResult(getTargetRequestCode(), resultCode, getActivity().getIntent());
dismiss();

就是这样。请注意,int resultCode被定义为 resultCode = 1; 在这种情况下。

现在你可以将DialogFragment的结果发送回调用它的Activity、Fragment或FragmentActivity中。

此前已有类似的信息发布,但没有给出充分的示例,因此我会提供更多详细信息。

编辑 06.24.2016对于上面误导的代码,我表示歉意。但显然不能像下面这行代码那样将结果发送回activity:

dialogFrag.setTargetFragment(this, 1);

要设置目标为Fragment而不是Activity。因此,您需要实现一个InterfaceCommunicator

在您的DialogFragment中设置一个全局变量。

public InterfaceCommunicator interfaceCommunicator;

创建一个公共函数来处理它

public interface InterfaceCommunicator {
    void sendRequestCode(int code);
}

当您准备将代码发送回Activity以在运行完成DialogFragment后执行时,只需在dismiss();之前添加以下行:

interfaceCommunicator.sendRequestCode(1); // the parameter is any int code you choose.

您现在的任务是要做两件事情,第一件是删除那一行不再适用的代码:

dialogFrag.setTargetFragment(this, 1);  

然后实现接口,就完成了。你可以通过将以下行添加到类顶部的implements子句中来实现:

public class MyClass Activity implements MyDialogFragment.InterfaceCommunicator

然后在活动中 @Override 函数。

@Override
public void sendRequestCode(int code) {
    // your code here
}

您可以像使用onActivityResult()方法一样使用此接口方法。只不过这个接口方法是用于DialogFragments,而另一个是用于Fragments


4
如果目标是Activity,则此方法将无法起作用,因为你无法调用其onActivityResult(从你的DialogFragment中),这是由于访问级别被保护。 - Ruslan Yanchyshyn
2
这完全不是真的。我在我的项目中使用了这个确切的代码。那就是我从中获取它并且它可以正常工作。请记住,如果您遇到受保护访问级别问题,您可以将任何方法和类的访问级别从protected更改为private或public(如果必要)。 - Brandon
6
你说你可以从Activity中调用dialogFrag.setTargetFragment(this, 1),但是这个方法的第一个参数应该是一个Fragment,所以不能进行强制类型转换。我的理解正确吗? - Daniel
1
我稍后会为大家发布一些回复,以解释活动相关的内容。 - Brandon
1
@Swift @lcompare 你可能需要在你的DialogFragment中重写onAttach(Context context)方法。像这样:@Override public void onAttach(Context context) { super.onAttach(context); yourInterface = (YourInterface) context; } - lidkxx
显示剩余7条评论

25

对于仍在阅读此文的任何人: 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()
}

2
随着Fragment库1.3.0的发布(https://developer.android.com/jetpack/androidx/releases/fragment#version_130_2),这将是“最正确”的答案。目前它仅通过alpha版本发布,不应在生产中使用。 - fklappan
2
parentFragmentManager 很重要。很容易出现错误地发送 childFragmentManager,这不会触发 setFragmentResultListener lambda 函数。 - sha
如何使用对话框片段(Dialog Fragment)以这种新的方式进行Result.ACTIVITY_CANCEL操作? - João Carlos
您可以使用fragment-ktx中提供的扩展:setFragmentResultListener - Ionut Negru

22

也许已经太晚回答了,但这是我从DialogFragment中获取结果的方式。非常类似于@brandon的答案。 在这里,我从片段(fragment)中调用DialogFragment,只需将此代码放置在调用对话框的位置即可。

FragmentManager fragmentManager = getFragmentManager();
            categoryDialog.setTargetFragment(this,1);
            categoryDialog.show(fragmentManager, "dialog");

这里,categoryDialog 是我想要调用的 DialogFragment,在您的 dialogfragment 实现中,将以下代码放置在您将数据设置到 intent 中的位置。 resultCode 的值为 1,您可以设置它或使用系统定义。

            Intent intent = new Intent();
            intent.putExtra("listdata", stringData);
            getTargetFragment().onActivityResult(getTargetRequestCode(), resultCode, intent);
            getDialog().dismiss();

现在是时候回到调用片段并实现此方法了。如果需要,请使用resultCoderequestCode检查数据的有效性或结果成功性。

 @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);        
        //do what ever you want here, and get the result from intent like below
        String myData = data.getStringExtra("listdata");
Toast.makeText(getActivity(),data.getStringExtra("listdata"),Toast.LENGTH_SHORT).show();
    }

11

为了允许 Fragment 与其上层的 Activity 进行通信,有不同的方法:

1) 在 Fragment 中定义一个公共接口,并创建一个变量。

public OnFragmentInteractionListener mCallback;

public interface OnFragmentInteractionListener {
    void onFragmentInteraction(int id);
}

2) 将活动转换为片段中的 mCallback 变量。

try {
    mCallback = (OnFragmentInteractionListener) getActivity();
} catch (Exception e) {
    Log.d(TAG, e.getMessage());
}

3) 在您的活动中实现监听器

public class MainActivity extends AppCompatActivity implements DFragment.OnFragmentInteractionListener  {
     //your code here
}

4) 在活动中覆盖 OnFragmentInteraction 方法

@Override
public void onFragmentInteraction(int id) {
    Log.d(TAG, "received from fragment: " + id);
}

更多关于此的信息: https://developer.android.com/training/basics/fragments/communicating.html


谢谢您总结得这么好。对于其他人,只有一个注意事项,Android Devs教程建议覆盖片段的public void onAttach并在那里进行活动转换。 - Big_Chair

10

我发现一个简单的方法是:

在你的DialogFragment中实现它,

  CallingActivity callingActivity = (CallingActivity) getActivity();
  callingActivity.onUserSelectValue("insert selected value here");
  dismiss();

然后在调用对话框片段的活动中,创建适当的函数,如下所示:

 public void onUserSelectValue(String selectedValue) {

        // TODO add your implementation.
      Toast.makeText(getBaseContext(), ""+ selectedValue, Toast.LENGTH_LONG).show();
    }

这个 Toast 是为了展示它的工作原理。对我来说起作用了。


我不确定这是否是正确的做法,但它确实有效 :) - soshial
最好使用“接口”而不是与具体类进行硬耦合。 - waqaslam

7
我很惊讶地发现没有人建议使用本地广播来实现 DialogFragmentActivity 的通信!相比其他建议,我觉得这种方式更加简单和清晰。基本上,您需要为您的 Activity 注册监听广播,并从您的 DialogFragment 实例发送本地广播即可。简单易行。有关如何设置它的逐步指南,请参见此处

2
我喜欢那个解决方案,这在Android中被认为是好的还是最佳实践? - Benjamin Scharbau
1
我真的很喜欢这个教程,谢谢你发布它。但我想补充说明的是,根据你尝试实现的内容不同,其中任何一种方法都可能更有用。如果你从对话框发送多个输入/结果返回到活动中,则建议使用本地广播路线。如果你的输出非常简单/基础,则建议使用onActivityResult路线。所以,回答最佳实践问题,这取决于你想要实现什么! - Brandon
1
@AdilHussain 你说得对。我假设人们在他们的活动中使用片段。如果您要与片段和DialogFragment通信,则setTargetFragment选项非常好。但是,当Activity调用DialogFragment时,您需要使用Broadcast方法。 - Brandon
3
为了Foo的爱,不要使用广播!!这会为您的应用程序带来一系列安全问题。我发现最糟糕的Android应用程序都滥用广播。你能想出更好的方法使代码完全无法使用吗?现在我必须清除广播接收器,而不是一行明确的代码?明确地说,广播有其用途,但不适用于这种情况!永远不要在这种情况下使用!它只是懒散。回调函数就是你所需要的。 - StarWind0
1
除了Guava EventBus之外,另一个选择是GreenRobot EventBus。我没有使用过Guava EventBus,但使用过GreenRobot EventBus,并且对其有良好的体验。易于使用,简单明了。如果想要了解如何构建Android应用程序以使用GreenRobot EventBus,请参见此处的小例子。 - Adil Hussain
显示剩余3条评论

4
或者像这里展示的那样共享ViewModel:
public class SharedViewModel extends ViewModel {
    private final MutableLiveData<Item> selected = new MutableLiveData<Item>();

    public void select(Item item) {
        selected.setValue(item);
    }

    public LiveData<Item> getSelected() {
        return selected;
    }
}


public class MasterFragment extends Fragment {
    private SharedViewModel model;
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        itemSelector.setOnClickListener(item -> {
            model.select(item);
        });
    }
}

public class DetailFragment extends Fragment {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        model.getSelected().observe(this, { item ->
           // Update the UI.
        });
    }
}

https://developer.android.com/topic/libraries/architecture/viewmodel#sharing_data_between_fragments


3

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

我希望这能够帮到您,如果您能改进它,请进行编辑。 我的英语水平不是很好。


在我的情况下,对话框是从片段而不是活动创建的,因此这个解决方案不起作用。但我喜欢你放的笑脸 :) - Gilbert

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