从DialogFragment接收结果

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

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

目前,我正在使调用活动实现DismissListener,并给予DialogFragment对活动的引用。然后Dialog调用活动中的OnDimiss方法,活动从DialogFragment对象中获取结果。非常混乱,它不能在配置更改(方向更改),因为DialogFragment失去了对活动的引用。

谢谢你的帮助。

131423 次浏览

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

这似乎是对onActivityResult()的滥用,特别是因为它根本不涉及活动。但我看到官方谷歌的人推荐它,甚至可能在api演示中。我认为这就是g/setTargetFragment()被添加的原因。

正如你所看到的在这里有一个非常简单的方法来做到这一点。

DialogFragment中添加一个接口监听器,如下所示:

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

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

private EditNameDialogListener listener;

这将被用来“激活”监听器方法,也用来检查父Activity/Fragment是否实现了这个接口(见下文)。

在“调用”DialogFragmentActivity/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");
}
}

这种技术非常灵活,即使你还不想结束对话框,也可以回调结果。

有一种更简单的方法来接收来自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(它将调用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);
}

然后,当你准备好在DialogFragment完成运行时将代码发送回Activity时,你只需在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

我很惊讶地发现,没有人建议使用本地广播进行DialogFragmentActivity的通信!我发现它比其他建议更简单、更清晰。本质上,你为你的Activity注册来监听广播,你从你的DialogFragment实例发送本地广播。简单。有关如何设置它的详细指南,请参见在这里

我发现了一个简单的方法: 实现这个是你的dialogFragment,

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

然后在调用Dialog Fragment的activity中创建相应的函数:

 public void onUserSelectValue(String selectedValue) {


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

祝酒词是为了证明它是有效的。为我工作。

好吧,可能为时已晚,但这是我所做的从DialogFragment得到结果。非常类似于@brandon的回答。 在这里,我从一个片段调用DialogFragment,只是把这段代码放在你调用对话框的地方

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

其中categoryDialog是我的DialogFragment,我想调用,在此之后,在你的dialogfragment的实现中,将此代码放置在你设置数据的意图。resultCode的值是1,你可以设置它或使用system Defined。

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

现在回到调用片段并实现该方法。如果你想在if条件中使用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();
}

在我的例子中,我需要将参数传递给一个targetFragment。但我发现异常"碎片已激活"所以我在我的DialogFragment中声明了一个接口,这是parentFragment实现的。当parentFragment启动一个DialogFragment时,它将自己设置为TargetFragment。然后在DialogFragment中调用

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

不同的方法,允许片段与它的活动通信:

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

在活动中实现监听器

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

只是把它作为选项之一(因为还没有人提到它)-你可以使用像Otto这样的事件总线。 所以在对话框中你可以这样做:

bus.post(new AnswerAvailableEvent(42));

并让你的调用者(Activity或Fragment)订阅它:

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

在芬兰湾的科特林

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

我希望它服务,如果你可以改进,请编辑它。 我的英语不是很好

或者像下面这样共享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

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

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

关于一个对话片段

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

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

val abcDialogFragment = AbcDialogFragment(ondata = {data->  })
                

abcDialogFragment.show(requireActivity().supportFragmentManager, "TAG")

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

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

详细解释:

  • 前提 -片段被销毁并在配置更改时重新创建。视图模型就挂在那里。当使用对话框时,建议将其包装在DialogFragment中,这样当用户旋转设备和改变方向时,对话框不会意外消失(DialogFragment将重新创建并重新显示它)。
  • 限制(因此有这个问题)- DialogFragment的工作方式是它接受一个类,它将需要在配置更改时重新实例化-这意味着不能有构造函数参数给子类来传递参数,通常需要通过视图模型进行自定义回调来传递dialog的结果。这通常意味着每个对话框都有一个新的子类。
  • 解决方案 -为了帮助解决这一切,这个自定义AppDialog片段来拯救-参数存储在内存中(类似于视图模型,你可以认为它是一个微小的自定义视图模型,在内存中保存T,并使用它在配置更改时重新创建对话框),直到对话框片段被解散。回调的正确方法是通过视图模型。如果片段显示了AppDialog,那么您可能已经有了一个视图模型,并且可以从用于创建对话框的lambda中引用它——这意味着对视图模型的额外强引用,直到对话框片段被解散。
  • 例子 -请参阅示例,其中一个简单的Dialog被重构为使用这个AppDialog实用程序类来接收参数并对viewModel进行回调以通知结果。

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