Возврат результата выполнения из DialogFragment во Fragment минуя Activity

Введение


В этой публикации я покажу, как можно передавать события из DialogFrament в вызывающий Fragment минуя Activity.

В официальном Guide по Dialogs есть раздел PassingEvents. В нем рассказано, как вернуть результат работы DialogFragment в вызывающую Activity. Для этого создается дополнительный интерфейс. Activity реализует этот интерфейс, а у DialogFrament есть ссылка на Activity.

Если инициатором вызова DialogFragment является другой Fragment, то в таком подходе нам придется сначала отправить результат в Activity, а потом уже из Activity в заинтересованный в данных Fragment. Отсюда и минусы:
  • Activity знает о деталях реализации фрагмента (сегодня это дополнительный диалог, а завтра можем реализовать все на одном фрагменте);
  • дополнительный код (больше кода = больше возможностей для ошибок).


К счастью, у класса Fragment есть 3 метода, которые позволят реализовать передачу событий из одного фрагмента в другой фрагмент минуя Activity. Это:

Суть метода


Вызывающий фрагмент:
  • с помощью setTargetFragment устанавливает себя в качестве targetFrament и устанавливает requestCode;
  • реализует метод onActivityResult в котором обрабатывает requestCode и resultCode, а также имеет доступ к дополнительным данным через intent.

Вызываемый фрагмент:
  • c помощью getTargetFrament получает ссылку на вызывающий фрагмент;
  • с помощью getTargetRequestCode получает код с которым он был вызыван;
  • вызывает onActivityResult вызывающего фрагмента и передает результаты своего выполнения через resultCode и Intent (для дополнительных).

Ниже пример кода. Для простоты оставил только актуальные для статьи части.

Вызывающий фрагмент:

public class HostFragment extends Fragment {
    private static final int REQUEST_WEIGHT = 1;
     private static final int REQUEST_ANOTHER_ONE = 2;
     public void openWeightPicker() {
        DialogFragment fragment = new WeightDialogFragment();
        fragment.setTargetFragment(this, REQUEST_WEIGHT);
        fragment.show(getFragmentManager(), fragment.getClass().getName());
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == Activity.RESULT_OK) {
            switch (requestCode) {
                case REQUEST_WEIGHT:
                         int weight = data.getIntExtra(WeightDialogFragment.TAG_WEIGHT_SELECTED, -1)
                         //используем полученные результаты
                         //...
                         break;
                    case REQUEST_ANOTHER_ONE:
                        //...
                         break;
                //обработка других requestCode
            }
            updateUI();
        }
    }
}

Вызываемый фрагмент:

public class WeightDialogFragment extends DialogFragment {
     //тэг для передачи результата обратно
    public static final String TAG_WEIGHT_SELECTED = "weight";
    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        LayoutInflater inflater = getActivity().getLayoutInflater();
        View view = inflater.inflate(..., null);
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        builder.setView(view)
                .setPositiveButton("Ok", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                             //отправляем результат обратно
                        Intent intent = new Intent();
                        intent.putExtra(TAG_WEIGHT_SELECTED, mNpWeight.getValue());
                        getTargetFragment().onActivityResult(getTargetRequestCode(), Activity.RESULT_OK, intent);
                    }
                });
        return builder.create();
    }

}

Заключение


Перечисленные в начале статьи минусы в таком подходе отсутствуют. Только два взаимодействующих элемента знают друг о друге. Для остальных это уже неизвестные детали реализации.

Мне бы хотелось узнать, о таком варианте реализации еще при изучении официального гида по диалогам. Я же узнал позже благодаря StackOverflow.
Поделиться публикацией
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама
Комментарии 15
  • +1
    А бывают ли реальные потребности отправлять события прямо во Activity? Может тогда и вызывать DialogFragment в самом Activity и имплементировать ему DialogFragment абстрактные методы?
    • 0
      А бывают ли реальные потребности отправлять события прямо во Activity?
      К примеру, если UI реализован в Activity без использования Fragment.
      Может тогда и вызывать DialogFragment в самом Activity и имплементировать ему DialogFragment абстрактные методы?
      На сколько я понимаю, речь об анонимном классе. Я бы такой подход не использовал т.к я вижу всего один сомнительный плюс и два существенных минуса.
      Сомнительный плюс: чуть меньше кода (не будет создания констант REQUEST_, не будет switch и не надо использовать intent).
      Минусы:
      • Анонимный класс хранит ссылку на внешний класс. Как результат, усложняется задача по обработке пересоздания фрагментов т.к. помимо сохранения/восстановления собственных данных нам нужно будет еще и эту ссылку воостанавливать. Решаемо конечно, но уже усложнение и дополнительная возможность ошибиться.
      • Весь код будет в вызывающих activity/fragment, а значит повторно использовать DialogFragment уже нельзя.
      • 0
        На сколько я помню, при пересоздании анонимного диалог фрагмента выбрасывается исключение ClassNotFoundException. Так что это не просто сомнительно, а крайне не желательно.
        • 0
          На сколько я понимаю, речь об анонимном классе.

          Нет, речь о абстрактном классе (не inner) DialogFragment, инстанс которого создается прямо в Activity по вызову такого же абстрактного метода во Fragment. Все эти абстрактные методы имплементируются на уровне инстанса, который нам надо слушать. По аналогии интерфейса.
          • 0
            Вы говорите, что это не анонимный класс, но то, что вы описываете и есть анонимный класс.
            Если я правильно понял, вы имели ввиду вот такую реализацию:
            DialogFragment dialog = new AbsDialogFragment() {
                @Override
                public void someAbstractMethod(String someString) {
                    // Implementation ...
                }
            };
            dialog.show(getFragmentManager(), "ImplDialogFragment");

            Это яркий пример анонимного класса.
      • 0
        Еще, для получения фрагмента, вызвавшего диалог, можно использовать метод getParentFragment(). Но для этого, при вызове диалога, нужно использовать ChildFragmentManager. Минус этого метода заключается в том, что для диалога нельзя будет установить setRetainInstance(true). Но как по мне так setRetainInstance(true) — это читерский прием, который может привести к кое-каким неприятностям в будущем, при уничтожении процесса.
        • 0
          Думаю этот подход лучше. Использую его везде.
          Во все проекты добавляю что-то похожее на такой BaseDialogFragment
          • 0
            Считаю, что getParentFragment() хорош если у нас один вызывающий фрагмент и один вызываемый фрагмент (отношение один к одному). Если же у нас несколько вызываемых фрагментов (отношение один ко многим), то необходимо иметь возможность различить их с помощью getTargetRequestCode(), который устанавливается через setTargetFragment.
            • 0
              RequestCode можно и через аргументы передать. А еще в ответе можно возвращать Tag фрагмента. Так что путей идентификации масса.
          • 0
            Популярное решение для связей один-к-одному между фрагментами. К сожалению, не масштабируется на другие случаи вне фрагментов или когда нужно оповестить несколько компонентов системы. Как результат, чтобы не городить отдельную систему коллбэков для каждого отдельного случая, рано или поздно приходят к более универсальному event bus паттерну. Как бонус — объекты вместо intent.
            • 0
              А я комбинирую вариант имплементации кастомного интерфейса и вариант с использованием методов setTargetFragment и getTargetFragment. Выглядит это так: хост-фрагмент (вызывающий диалог) имплементирует интерфейс-колбек, далее в диалоге он устанавливается как target-фрагмент, при возврате результата getTargetFragment кастится к интерфейсу и дергается колбек. Плюсы такого подхода перед описываемым — отсутствие необходимости упаковывать-распаковывать результаты в intent, более очевидный и чистый код.
              • 0
                Полный код такого фрагмента тут
                Он позволяет создавать как простые фрагменты-диалоги типа
                new ConfirmDialogFragment.Builder(this)
                    .title(R.string.menu_logout_confirm_dialog_title)
                    .message(R.string.menu_logout_confirm_dialog_text)
                    .show();
                

                … так и более сложные с передачей диалог-тега (для идентификации конкретного диалога в колбеке), установкой стиля, передачи дополнительных данных (cookie), которые будут доступны в колбеке.
                new ConfirmDialogFragment.Builder(this)
                    .title(R.string.menu_logout_confirm_dialog_title)
                    .message(R.string.menu_logout_confirm_dialog_text)
                    .okButton(R.string.button_logout)
                    .dialogTag("confirmLogout")
                    .cookie("user", user)
                    .destructive()
                    .show();
                


                Все это нормально переживает пересоздание активити.
              • +1
                Хорошее решение, но работает только, если оба фрагмента находятся в бек-стеке.
                Если это не будет удовлетворено, то фрагмент менеджер упадет при изменении конфигурации (target not in fragment manager).
                • 0
                  Понимаю, что вопрос немного не по адресу, но все же. При использовании DialogFragment (из саппорт либы) у меня на девайсах старше киткат 4.4.4 вместо тени — черная рамка вокруг диалога, а без DialogFragment — все работает. Может быть встречалось такое?
                  • 0
                    Спасибо, тебе добрый человек, за статью.
                    Вчера пол-ночи возился с такой задачей.
                    Причем, раньше так делал — но напрочь забыл.

                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                    Интересные публикации