Слушатель DialogFragment, который сохраняется при изменениях конфигурации - PullRequest
0 голосов
/ 07 января 2019

Сценарий выглядит следующим образом, у меня есть ViewPager, который содержит фрагменты, каждый из этих фрагментов имеет некоторые действия, которые требуют подтверждения.

Я продолжаю создавать фрагмент DialogFragment для фрагмента, который также знает, как обрабатывать результат, однако фрагмент может быть воссоздан до того, как пользователь подтвердит или отклонит диалоговое окно.

Я мог бы передать лямбду или какую-либо другую форму слушателя в диалог, который затем вызывался бы, когда пользователь подтверждает диалог, но проблема в том, что если устройство затем поворачивается, лямбда теряется, так как не может настаивать на связке ...

Единственный способ, о котором я могу думать, это назначить некоторый UUID диалоговому окну и подключить UUID в приложении к лямбде, который хранится на Map внутри приложения, однако это выглядит очень неаккуратным решением.

Я пытался найти существующие решения в Интернете, например, пример библиотек материалов-диалогов , но в большинстве случаев кажется, что диалоговое окно при вращении отклоняется, но это также выглядит как неаккуратное решение, так как диалоговое окно может быть частью более длинного потока, такого как

запросить покупку -> отменить -> показать диалог с объяснением -> снова купить, если пользователь хочет

где состояние потока будет потеряно, если мы просто отклоним диалог о вращении

Ответы [ 5 ]

0 голосов
/ 12 февраля 2019

Слушатель произведет некоторую кодовую связь, в вашем случае, почему бы не использовать шину событий. Внутренне событийная шина работает как слушатели, но вам не нужно ничего делать самостоятельно. Ниже приведены инструкции по использованию шины событий. Создать объект события (лучше поддерживать его чистым объектом)

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

А для МАМА Градл: D

implementation "org.greenrobot:eventbus:3.1.1"
0 голосов
/ 11 февраля 2019

Вы можете использовать ViewModel:

Класс ViewModel предназначен для хранения и управления данными, связанными с пользовательским интерфейсом, с учетом жизненного цикла. Класс ViewModel позволяет данным пережить изменения конфигурации , такие как поворот экрана .

В документации также рассматриваются фрагменты в разделе Share data between fragments.

... Эту общую болевую точку можно устранить с помощью объектов ViewModel. Эти фрагменты могут совместно использовать ViewModel, используя их область действия для обработки этого сообщения ...

Интересная часть может быть:

Обратите внимание, что оба фрагмента извлекают действие, которое их содержит. Таким образом, когда каждый из фрагментов получает ViewModelProvider, они получают один и тот же экземпляр SharedViewModel , который ограничен этим действием.

Смотрите ниже, как viewModel выживает при повороте экрана. enter image description here

0 голосов
/ 06 февраля 2019

Лучший способ обработки диалогов, которые я нашел, - это EventBus. Вы в основном отправляете события из диалогов и перехватываете их в действиях / фрагментах.

Вы можете назначить идентификаторы диалогам после их создания и добавить этот идентификатор в события, чтобы различать события из разных диалогов (даже если диалоги относятся к одному и тому же типу).

Вы можете увидеть, как работает эта схема, и получить дополнительные идеи, просмотрев код здесь . Вы также можете найти этот вспомогательный класс , который я написал полезным (хотя будьте осторожны с ним, потому что этот код очень старый; например, я больше не сохраняю диалоги).

Для полноты ответа я выложу здесь несколько фрагментов. Обратите внимание, что эти фрагменты уже используют новую 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));
    }
}
0 голосов
/ 11 февраля 2019

Если вы передадите анонимный лямбда / прослушиватель, вы потеряете его после поворота, но если вы заставите свою деятельность реализовать слушателя и назначить ее в 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
    }
}
0 голосов
/ 06 февраля 2019

Вместо использования обратных вызовов для захвата ссылки на целевой объект, попробуйте LocalBroadcastManager ( docs ).

Основные преимущества этого метода:

  1. Никаких дополнительных зависимостей для вашего проекта, потому что LocalBroadcastManager является частью support-v4 и AndroidX legacy-support-v4, которые, скорее всего, у вас уже есть.

  2. Нет необходимости хранить какие-либо ссылки.

В двух словах:

  • В DialogFragment вместо вызова обратного вызова вы отправляете Intent с сообщением через LocalBroadcastManager и
  • В целевом фрагменте вместо передачи обратного вызова в DialogFragment вы используете BroadcastReceiver для прослушивания сообщений, которые проходят через LocalBroadcastManager.

Для отправки из 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);
}
...