DialogFragment侦听器,在配置更改时仍然存在

时间:2019-01-07 11:49:42

标签: android android-fragments dialog android-dialogfragment

场景如下,我有一个包含片段的ViewPager,这些片段中的每一个都有一些需要确认的操作。

我继续创建一个针对片段的DialogFragment,该片段也知道如何处理结果,但是可以在用户确认或拒绝对话框之前重新创建该片段。

我可以将lambda或某种其他形式的侦听器传递给对话框,然后在用户确认对话框时调用该lambda,但问题是如果旋转设备,则lambda将丢失,因为它不能坚持下去...

我能想到的唯一方法是为对话框分配一些UUID,然后将应用程序中的UUID连接到lambda,后者保存在应用程序内部的Map上,但这似乎是很草率的解决方案。

我尝试在线搜索现有的解决方案,例如material-dialogs librarys sample,但是大多数情况下似乎忽略了轮换对话框,但这似乎也很草率,因为对话框可能是更长的一部分流,例如

请求购买->取消->显示带有说明的对话框->如果用户愿意,请再次购买

如果我们仅忽略旋转对话框,则会丢失流的状态

5 个答案:

答案 0 :(得分:2)

如果您传递匿名lambda / Listener,您将在轮换后丢失它,但是如果您使自己的活动实现监听器并以片段的onAttach(context)方法进行分配,则将在重新创建活动后重新分配它。

interface FlowStepListener {
    fun onFirstStepPassed()
    fun onSecondStepPassed()
    fun onThirdStepPassed()
}
class ParentActivity: Activity(), FlowStepListener {
    override fun onFirstStepPassed() {
        //control your fragments here
    }
    override fun onSecondStepPassed() {
        //control your fragments here
    }
    override fun onThirdStepPassed() {
        //control your fragments here
    }
}
open class BaseDialogFragment : DialogFragment() {
    var listener: FlowStepListener? = null

    override fun onAttach(context: Context) {
        super.onAttach(context)
        if (context is FlowStepListener) {
            listener = context
        } else {
            throw RuntimeException("$context must implement FlowStepListener")
        }
    }

    override fun onDetach() {
        super.onDetach()
        listener = null
    }
}

答案 1 :(得分:0)

请尝试使用LocalBroadcastManagerdocs),而不要使用回调来捕获对目标对象的引用。

此方法的主要优点是:

  1. 您的项目没有任何额外的依赖关系,因为LocalBroadcastManager是support-v4和(或)AndroidX的legacy-support-v4(您很可能已经拥有)的一部分。

  2. 无需保留任何类型的引用。

简而言之:

  • 在DialogFragment中,而不是调用回调,而是通过Intent发送LocalBroadcastManager和消息,然后
  • 在目标Fragment中,您无需使用回调函数BroadcastReceiver来监听通过LocalBroadcastManager传递的消息,而无需将回调传递给DialogFragment。

用于从DialogFragment内部发送:

public static final String MY_ACTION = "DO SOMETHING";

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
  final Button button = view.findViewById(R.id.accept);
    button.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent broadcastIntent = new Intent(MY_ACTION);
            LocalBroadcastManager.getInstance(getContext()).sendBroadcast(broadcastIntent);
            dismiss();
        }
    });
}

并用于侦听目标片段中的消息:

private final BroadcastReceiver localReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        // Do whatever you need to do here
    }
};

@Override
protected void onStart() {
    super.onStart();

    final IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction(MyDialogFragment.MY_ACTION);
    LocalBroadcastManager.getInstance(getContext())
        .registerReceiver(localReceiver, intentFilter);
}

@Override
protected void onStop() {
    super.onStop();
    LocalBroadcastManager.getInstance(this)
        .unregisterReceiver(localReceiver);
}

答案 2 :(得分:0)

处理我发现的对话框的最佳方法是使用EventBus。您基本上是从对话框发送事件,并在“活动/片段”中拦截它们。

您可以在实例化时为对话框分配ID,并将此ID添加到事件中,以区分来自不同对话框的事件(即使对话框来自同一类型)。

通过查看the code here,您可以了解此方案的工作原理并获得其他一些想法。您还可以发现我写过的this helper class很有用(尽管要小心,因为这段代码很旧;例如,我不再保留对话框了。)

为使答案完整,我将在此处发布一些摘要。请注意,这些片段已经使用了新的FragmentFactory,因此对话框具有构造函数参数。这是相对较新的功能,因此您的代码可能不会使用它。

这可能是显示一些信息并具有一个按钮的对话框的实现。您想知道何时关闭此对话框:

public class InfoDialog extends BaseDialog {

    public static final String ARG_TITLE = "ARG_TITLE";
    public static final String ARG_MESSAGE = "ARG_MESSAGE";
    public static final String ARG_BUTTON_CAPTION = "ARG_POSITIVE_BUTTON_CAPTION";

    private final EventBus mEventBus;

    private TextView mTxtTitle;
    private TextView mTxtMessage;
    private Button mBtnPositive;

    public InfoDialog(EventBus eventBus) {
        mEventBus = eventBus;
    }

    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getContext());
        LayoutInflater inflater = LayoutInflater.from(getContext());
        View dialogView = inflater.inflate(R.layout.dialog_info_prompt, null);
        dialogBuilder.setView(dialogView);

        initSubViews(dialogView);

        populateSubViews();

        setCancelable(true);

        return dialogBuilder.create();
    }

    private void initSubViews(View rootView) {
        mTxtTitle = (TextView) rootView.findViewById(R.id.txt_dialog_title);
        mTxtMessage = (TextView) rootView.findViewById(R.id.txt_dialog_message);
        mBtnPositive = (Button) rootView.findViewById(R.id.btn_dialog_positive);

        // Hide "negative" button - it is used only in PromptDialog
        rootView.findViewById(R.id.btn_dialog_negative).setVisibility(View.GONE);

        mBtnPositive.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dismiss();
            }
        });

    }

    private void populateSubViews() {
        String title = getArguments().getString(ARG_TITLE);
        String message = getArguments().getString(ARG_MESSAGE);
        String positiveButtonCaption = getArguments().getString(ARG_BUTTON_CAPTION);

        mTxtTitle.setText(TextUtils.isEmpty(title) ? "" : title);
        mTxtMessage.setText(TextUtils.isEmpty(message) ? "" : message);
        mBtnPositive.setText(positiveButtonCaption);
    }

    @Override
    public void onDismiss(DialogInterface dialog) {
        super.onDismiss(dialog);
        mEventBus.post(new InfoDialogDismissedEvent(getDialogTag()));
    }
}

此对话框为用户提供了两个选项之间的选择:

public class PromptDialog extends BaseDialog {

    public static final String ARG_TITLE = "ARG_TITLE";
    public static final String ARG_MESSAGE = "ARG_MESSAGE";
    public static final String ARG_POSITIVE_BUTTON_CAPTION = "ARG_POSITIVE_BUTTON_CAPTION";
    public static final String ARG_NEGATIVE_BUTTON_CAPTION = "ARG_NEGATIVE_BUTTON_CAPTION";

    private final EventBus mEventBus;

    private TextView mTxtTitle;
    private TextView mTxtMessage;
    private Button mBtnPositive;
    private Button mBtnNegative;

    public PromptDialog(EventBus eventBus) {
        mEventBus = eventBus;
    }

    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getContext());
        LayoutInflater inflater = LayoutInflater.from(getContext());
        View dialogView = inflater.inflate(R.layout.dialog_info_prompt, null);
        dialogBuilder.setView(dialogView);

        initSubViews(dialogView);

        populateSubViews();

        setCancelable(false);

        return dialogBuilder.create();
    }

    private void initSubViews(View rootView) {
        mTxtTitle = (TextView) rootView.findViewById(R.id.txt_dialog_title);
        mTxtMessage = (TextView) rootView.findViewById(R.id.txt_dialog_message);
        mBtnPositive = (Button) rootView.findViewById(R.id.btn_dialog_positive);
        mBtnNegative = (Button) rootView.findViewById(R.id.btn_dialog_negative);

        mBtnPositive.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dismiss();
                mEventBus.post(new PromptDialogDismissedEvent(getDialogTag(), PromptDialogDismissedEvent.BUTTON_POSITIVE));
            }
        });

        mBtnNegative.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dismiss();
                mEventBus.post(new PromptDialogDismissedEvent(getDialogTag(), PromptDialogDismissedEvent.BUTTON_NEGATIVE));
            }
        });
    }

    private void populateSubViews() {
        String title = getArguments().getString(ARG_TITLE);
        String message = getArguments().getString(ARG_MESSAGE);
        String positiveButtonCaption = getArguments().getString(ARG_POSITIVE_BUTTON_CAPTION);
        String negativeButtonCaption = getArguments().getString(ARG_NEGATIVE_BUTTON_CAPTION);

        mTxtTitle.setText(TextUtils.isEmpty(title) ? "" : title);
        mTxtMessage.setText(TextUtils.isEmpty(message) ? "" : message);
        mBtnPositive.setText(positiveButtonCaption);
        mBtnNegative.setText(negativeButtonCaption);
    }

    @Override
    public void onCancel(DialogInterface dialog) {
        dismiss();
        mEventBus.post(new PromptDialogDismissedEvent(getDialogTag(), PromptDialogDismissedEvent.BUTTON_NONE));
    }
}

答案 3 :(得分:0)

您可以使用ViewModel

  

ViewModel类旨在以生命周期感知的方式存储和管理与UI相关的数据。 ViewModel类允许数据在配置更改(例如屏幕旋转)中幸免。

文档还介绍了Share data between fragments部分中的片段。

  

...此常见的痛点可以通过使用ViewModel对象解决。这些片段可以使用其活动范围共享一个ViewModel来处理这种通信...

有趣的部分可能是:

  

请注意,两个片段都检索包含它们的活动。这样,当每个片段都获得ViewModelProvider时,它们会收到相同的SharedViewModel实例,该实例的作用范围是此活动。

请参见下面的viewModel如何在屏幕旋转中幸存下来。 enter image description here

答案 4 :(得分:0)

Listener将产生一些代码耦合,在您的情况下,为什么不使用事件总线。内部事件总线的工作方式类似于侦听器,但您不必自己管理任何事情。以下是使用事件总线的步骤。 创建一个事件对象(最好保持一个对象的干净)

    public class DialogDataEvent {

    String someData;  

    public DialogDataEvent(String data){
    this.someData=data;

}

    }

然后发布您的活动

EventBus.getDefault().post(new DialogDataEvent("data"));

并在“活动/片段”中接收它

 @Subscribe
    public void onEvent(DialogDataEvent event) {
   //Do Some work here

    }

别忘了在接收类中注册和注销事件总线

@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (!EventBus.getDefault().isRegistered(this)) {
            EventBus.getDefault().register(this);
        }

    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        EventBus.getDefault().unregister(this);
    }

对于 MAMA Gradle:D

implementation "org.greenrobot:eventbus:3.1.1"