ChatGPT解决这个技术问题 Extra ChatGPT

从 DialogFragment 接收结果

我将 DialogFragments 用于许多事情:从列表中选择项目,输入文本。

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

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

谢谢你的帮助。

DialogFragments 仍然只是片段。您的方法实际上是片段用来与主要活动对话的推荐方式。 developer.android.com/guide/topics/fundamentals/…
感谢那。我非常接近(如你所说)。该链接文档帮助我的一点是使用 onAttach() 并将活动投射到侦听器。
@codinguser,@Styx - “为 DialogFragment 提供对活动的引用” - 这个细节有点冒险,因为 ActivityDialogFragment 都可能被重新创建。使用传递给 onAttach(Activity activity)Activity 是正确且推荐的方法。
在这里查看我的答案stackoverflow.com/questions/55946219/…

w
whlk

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

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


如果目标是一项活动怎么办?
如果目标是活动,我将使用“void onActivityResult2(int requestCode, int resultCode, Intent data)”之类的方法声明接口并通过 Activity 实现它。在 DialogFragment 中,只需 getActivity 并检查此接口并适当地调用它。
这不是一个好的解决方案。保存和恢复对话框片段状态后它将不起作用。 LocalBroadcastManager 在这种情况下是最好的解决方案。
@Nik 那不是真的。这是最好的解决方案。保存和恢复状态时没有问题。如果您遇到问题,您使用了错误的片段管理器。目标片段/调用者必须使用 getChildFragmentManager() 来显示对话框。
setTargetFragment 现在已弃用,但替代品 FragmentManager.setFragmentResultListener(在 Pass data between fragments 中描述仍处于 alpha 阶段。
A
Aleksandar Stefanović

正如您所看到的 here,有一种非常简单的方法可以做到这一点。

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

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


这适用于 ActivityFragmentActivity,但如果调用者是 Fragment
我不确定我是否完全理解你。但如果调用者是 Fragment,它的工作方式相同。
如果调用者是 Fragment,那么您可以执行以下操作: 1. 将片段作为引用传递(可能不是一个好主意,因为您可能会导致内存泄漏)。 2. 使用 FragmentManager 并调用 findFragmentByIdfindFragmentByTag 它将获取您的活动中存在的片段。我希望它有所帮助。祝你有美好的一天!
这种方法的问题是片段不是很好地保留对象,因为它们是要重新创建的,例如尝试改变方向,操作系统将重新创建片段,但侦听器的实例将不再可用
@LOG_TAG 看看@Timmmm 的回答。 setTargetFragment()getTargetFragment() 很神奇。
B
Brandon

有一种更简单的方法可以从 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(一旦 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);
}

然后,当您准备好在 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


如果目标是 Activity,则此方法将不起作用,因为由于受保护的访问级别,您无法调用其 onActivityResult(从您的 DialogFragment)。
那明显是错的。我在我的项目中使用这个确切的代码。那是我从中拉出来的,它工作得很好。请记住,如果您遇到此受保护的访问级别问题,您可以在必要时将任何方法和类的访问级别从受保护更改为私有或公共。
您好,您说您可以从 Activity 调用 dialogFrag.setTargetFragment(this, 1),但此方法接收 Fragment 作为第一个参数,因此无法强制转换。我对吗 ?
我会在几篇文章中为大家发布一些回复,以解释活动内容。
@Swift @lcompare 您可能需要在 DialogFragment 中覆盖 onAttach(Context context) 。像这样:@Override public void onAttach(Context context) { super.onAttach(context); yourInterface = (YourInterface) context; }
A
A J

好吧,回答可能为时已晚,但这是我为从 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();

现在是时候回到调用片段并实现这个方法了。如果您希望在 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();
    }

s
stkent

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

随着片段库 1.3.0 (developer.android.com/jetpack/androidx/releases/…) 的发布,这将是“最正确”的答案。目前它只能通过不应在生产中使用的 alpha 版本获得。
parentFragmentManager 很重要。很容易意外发送 childFragmentManager 不会触发 setFragmentResultListener lambda
如何以这种新方式使用对话框片段执行 Result.ACTIVITY_CANCEL?
l
lcompare

不同的方法,以允许 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 Devs 教程建议覆盖片段的 public void onAttach 并在那里进行活动投射
Z
ZeWolfe15

我发现的一种简单方法如下:实现这是您的 dialogFragment,

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

然后在调用 Dialog Fragment 的活动中创建适当的函数,如下所示:

 public void onUserSelectValue(String selectedValue) {

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

Toast 是为了证明它有效。为我工作。


我不确定这是否是正确的方法,但它确实有效:)
最好使用 Interface 而不是与具体类硬耦合。
A
Adil Hussain

看到没有人建议使用本地广播进行 DialogFragmentActivity 通信,我感到非常惊讶!我发现它比其他建议更简单、更干净。本质上,您注册您的 Activity 以收听广播并从您的 DialogFragment 实例发送本地广播。简单的。有关如何进行全部设置的分步指南,请参阅 here


我喜欢那个解决方案,这在 Android 中被认为是一个好的或最佳实践吗?
我真的很喜欢这个教程,谢谢你发布它。我确实想补充一点,这取决于您要完成的工作,其中一种方法可能比另一种更有用。如果您有多个输入/结果从对话框发送回活动,我会建议使用本地广播路由。如果您的输出非常基本/简单,我建议您使用 onActivityResult 路由。因此,要回答最佳实践问题,这取决于您要完成的任务!
@AdilHussain 你是对的。我假设人们在他们的活动中使用片段。如果您正在与 Fragment 和 DialogFragment 进行通信,那么 setTargetFragment 选项非常有用。但是当它是一个调用DialogFragment的Activity时,你需要使用Broadcast方法。
为了 Foo 的爱,不要使用广播!它会使您的应用程序面临一系列安全问题。我还发现我必须在滥用广播上工作的最糟糕的 android 应用程序。你能想出一个更好的方法让代码完全无法使用吗?现在我必须根除广播接收器,而不是清除一行代码?需要明确的是,广播有一些用途,但不是在这种情况下!永远不要在这种情况下!它只是草率。本地与否。回调就是你所需要的。
除了 Guava EventBus 之外,另一个选项是 GreenRobot EventBus。我没有使用过 Guava EventBus,但使用过 GreenRobot EventBus 并有很好的体验。好用又简单。有关如何构建 Android 应用程序以使用 GreenRobot EventBus 的小示例,请参阅 here
M
Malachiasz

或共享 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


L
Lanitka

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

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

B
Bosko Mijin

在科特林

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

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


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

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

A
Ayush Sth

在对话片段上

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

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

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

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


E
Evtim Georgiev

TL;DR - 使用此 AppDialog 类将数据传递到 DialogFragment 并从中获取结果。

详细解释:

前提 - 片段在配置更改时被销毁并重新创建。查看模型徘徊。使用 Dialog 时,建议将其包装在 DialogFragment 中,这样当用户旋转设备并改变方向时,Dialog 不会意外消失(DialogFragment 会重新创建并重新显示它)。

限制(因此这个问题) - DialogFragment 的工作方式是它需要一个需要重新实例化配置更改的类 - 这意味着不能将构造函数参数传递给子类来传递参数,通常需要通过视图模型进行自定义回调以传回对话框的结果。这通常意味着每个对话框都有一个新的子类。

解决方案 - 为了帮助解决这一切,这个自定义 AppDialog 片段来救援 - 参数存储在内存中(类似于视图模型,您可以将其视为将 T 保存在内存中并使用它的微型自定义视图模型在配置更改时重新创建对话框),直到对话框片段被解除。回调的正确方法是通过视图模型。如果显示 AppDialog 的片段,那么您可能已经有一个视图模型,并且您可以从用于创建对话框的 lambda 中引用它 - 这意味着对视图模型的额外强引用,直到对话框片段被解除。

示例 - 请参阅重构简单 Dialog 以使用此 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()

D
Dennis K

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

bus.post(new AnswerAvailableEvent(42));

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

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