从DialogFragment接收结果


232

我正在使用DialogFragments做很多事情:从列表中选择项目,输入文本。

将值(即列表中的字符串或项目)返回到调用活动/片段的最佳方法是什么?

目前,我正在实现调用活动的实现DismissListener,并为DialogFragment提供了对该活动的引用。然后,对话框将调用OnDimiss活动中的方法,活动将从DialogFragment对象获取结果。非常混乱,由于DialogFragment丢失了对活动的引用,因此无法进行配置更改(方向更改)。

谢谢你的帮助。


10
DialogFragments仍然只是片段。实际上,您的方法是片段用于回覆主要活动的推荐方法。developer.android.com/guide/topics/fundamentals/...
codinguser

1
感谢那。我离你很近(如你所说)。链接文档帮助我的一点是使用onAttach()并将活动投射到侦听器。
James Cross

2
@codinguser,@Styx - “给DialogFragment到活动中的引用” -这个细节是有点冒险,因为两者的ActivityDialogFragment可能会重新创建。使用Activity传递给的onAttach(Activity activity)是正确的建议方法。
sstn

Answers:


246

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

似乎是对的滥用onActivityResult(),尤其是因为它根本不涉及任何活动。但是我已经看到它被谷歌官方推荐,甚至在api演示中也是如此。我认为这就是g/setTargetFragment()添加的目的。


2
setTargetFragment提到requestcode用于onActivityResult中,因此我想可以使用这种方法。
Giorgi 2013年

87
如果目标是一项活动怎么办?
费尔南多·加列戈2014年

9
如果目标是活动,那么我将使用“ void onActivityResult2(int requestCode,int resultCode,Intent data)”方法声明接口,并由Activity实现它。在DialogFragment中,只需getActivity并检查此接口并适当地调用它。
Ruslan Yanchyshyn 2014年

4
这不是一个好的解决方案。保存和还原对话框片段状态后,它将不起作用。在这种情况下,LocalBroadcastManager是最佳解决方案。
Nik

4
@Nik这不是真的。这是最好的解决方案。保存和还原状态没有问题。如果您遇到问题,请使用错误的片段管理器。目标片段/调用者不必使用getChildFragmentManager()来显示对话框。
令人难以置信的

140

如您所见,这里有一种非常简单的方法。

在您DialogFragment添加接口侦听器,如:

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

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

private EditNameDialogListener listener;

这将用于“激活”侦听器方法,还用于检查父级Activity / Fragment是否实现了此接口(请参见下文)。

Activity/ FragmentActivity/ Fragment说:“所谓的”DialogFragment简单地实现这个接口。

在您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");
    }
}

该技术非常灵活,即使您还不想关闭对话框,也可以使用结果进行回叫。


13
Activity'和和FragmentActivity' 一起使用时效果很好,但是调用方是否为a Fragment
Brais Gabin 2013年

1
我不确定我是否完全了解您。但是,如果调用方是,它将起作用Fragment
Assaf Gamliel 2013年

2
如果调用方是a,Fragment则您可以执行以下操作:1.将片段作为参考传递(可能不是一个好主意,因为这可能会导致内存泄漏)。2.使用FragmentManager和调用findFragmentByIdfindFragmentByTag否则它将获取您的活动中存在的片段。希望对您有所帮助。祝你有美好的一天!
Assaf Gamliel

5
这种方法的问题在于,片段不是很善于保留对象,因为它们是要重新创建的,例如,尝试更改方向,操作系统将重新创建片段,但侦听器的实例将不再可用
Necronet

5
@LOG_TAG查看@Timmmm的答案。setTargetFragment()而且getTargetFragment()是魔术。
Brais Gabin 2013年

48

从DialogFragment接收结果的方法要简单得多。

首先,需要在“活动”,“片段”或“片段活动”中添加以下信息:

@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(一旦关闭Dialog,它将调用onActivityResult(...),然后显示该对话框。就是这么简单。

现在,在DialogFragment中,您只需要在 dismiss()以便将resultCode发送回onActivityResult()。

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

而已。注意,在这种情况下,resultCode定义为int resultCode我设置的结果resultCode = 1;

就是这样,您现在可以将DialogFragment的结果发送回调用的Activity,Fragment或FragmentActivity。

另外,该信息似乎是先前发布的,但是没有给出足够的示例,因此我想提供更多详细信息。

编辑06.24.2016 我为上面的误导性代码表示歉意。但是您肯定不能将结果返回到活动中,如以下行所示:

dialogFrag.setTargetFragment(this, 1);

设定目标Fragment而不是Activity。因此,为了执行此操作,您需要使用工具InterfaceCommunicator

在您DialogFragment的全局变量中

public InterfaceCommunicator interfaceCommunicator;

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

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

然后,当你已经准备好发送代码回Activity的时候DialogFragment完成运行时,你只需在你面前添加行dismiss();DialogFragment

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 2014年

2
那明显是错的。我在我的项目中使用此确切的代码。那是我从中取出它的地方,并且效果很好。请记住,如果遇到此受保护的访问级别问题,可以根据需要将任何方法和类的访问级别从受保护更改为私有或公共。
布兰登2014年

6
嗨,您说可以dialogFrag.setTargetFragment(this, 1)从Activity 调用,但是此方法将Fragment作为第一个参数接收,因此无法进行强制转换。我对吗 ?
2015年

1
我将在少数几个中为大家发布一些回应,以解释活动的内容。
布兰登

1
@Swift @lcompare您可能需要在DialogFragment中重写onAttach(Context context)。像这样:@Override public void onAttach(Context context) { super.onAttach(context); yourInterface = (YourInterface) context; }
lidkxx

20

好了,也许现在来回答还为时已晚,但这是我为从中获得结果而做的事情DialogFragment。与@brandon的答案非常相似。在这里,我是DialogFragment从片段中调用的,只需将此代码放在调用对话框的位置即可。

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

我要在哪里调用categoryDialog我的代码,DialogFragment在此之后在您的实现中dialogfragment将此代码放置在您意图设置数据的位置。的值resultCode是1,您可以设置或使用系统定义。

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

现在该回到调用片段并实现此方法了。如果需要with resultCoderequestCodein if条件,请检查数据有效性或结果是否成功。

 @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();
    }

10

允许Fragment与其Activity进行通信的不同方法:

1)在片段中定义一个公共接口,并为其创建一个变量

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开发者教程仅建议其他注意事项,建议覆盖public void onAttach该片段的片段并在那里进行活动投射
Big_Chair

8

我发现的一种简单方法是:实现这是您的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

最好使用Interface而不是与具体类进行硬耦合。
waqaslam

6

我很惊讶的看着利用当地广播,对于没有人提出DialogFragmentActivity沟通!我发现它比其他建议更简单,更干净。本质上,您注册Activity要侦听广播,然后从DialogFragment实例发送本地广播。简单。有关如何进行全部设置的分步指南,请参见此处


2
我喜欢该解决方案,这被认为是Android的一种最佳做法还是最佳做法?
本杰明·沙尔鲍

1
我真的很喜欢该教程,感谢您发布它。我确实想补充一点,这取决于您要完成的任何一种方法可能比另一种有用。如果您有多个输入/结果从对话框发送回活动,我建议使用本地广播路径。如果您的输出非常基本/简单,我建议您使用onActivityResult路由。因此,要回答最佳实践问题,取决于您要实现的目标!
布兰登2015年

1
@AdilHussain你是对的。我假设人们在活动中使用片段。如果要与Fragment和DialogFragment通信,则setTargetFragment选项非常有用。但是当它是一个调用DialogFragment的Activity时,您需要使用Broadcast方法。
布兰登2015年

3
为了爱Foo,请不要使用广播!!它使您的应用程序遇到许多安全问题。我还发现我必须处理滥用广播的最差的android应用程序。您能想到一种使代码完全不可用的更好方法吗?现在,我必须清除广播接收器,而不是清除代码行吗?需要明确的是,宽泛海岸有用途,但在这种情况下不行!绝对不要在这种情况下!它只是马虎。是否本地。只需回调即可。
StarWind0'1

1
和Guava EventBus一样,另一个选择是GreenRobot EventBus。我没有使用过Guava EventBus,但是已经使用了GreenRobot EventBus,并且对它有很好的经验。尼斯和易于使用。有关如何构建Android应用程序以使用GreenRobot EventBus的小示例,请参见此处
阿迪尔·侯赛因

3

或共享如下所示的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


2

就我而言,我需要将参数传递给targetFragment。但是我得到了异常“片段已经激活”。所以我在DialogFragment中声明了一个由ParentFragment实现的接口。当parentFragment启动DialogFragment时,它会将自己设置为TargetFragment。然后在DialogFragment我叫

 ((Interface)getTargetFragment()).onSomething(selectedListPosition);

2

在科特林

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

希望它有用,如果您可以改进,请对其进行编辑。我的英文不是很好


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

0

如果要发送参数并从第二个片段接收结果,则可以使用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");
}

-3

只是将其作为选项之一(因为至今还没有人提到)-您可以使用Otto这样的事件总线。因此,在对话框中,您可以执行以下操作:

bus.post(new AnswerAvailableEvent(42));

并让您的呼叫者(活动或片段)订阅它:

@Subscribe public void answerAvailable(AnswerAvailableEvent event) {
   // TODO: React to the event somehow!
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.