- Возврат результата выполнения из DialogFragment во Fragment минуя Activity
- Введение
- Суть метода
- Заключение
- Как правильно закрыть DialogFragment?
- 5 ответов
- Свежий взгляд на отображение диалогов в Android
- Решение «в лоб»
- Устаревший способ
- Способ из документации
- Поиск идеального решения
- Реактивный способ
- Пишите диалоги правильно
- Урок 110. Android 3. Fragments. DialogFragment — диалог
Возврат результата выполнения из DialogFragment во Fragment минуя Activity
Введение
В этой публикации я покажу, как можно передавать события из DialogFrament в вызывающий Fragment минуя Activity.
В официальном Guide по Dialogs есть раздел PassingEvents. В нем рассказано, как вернуть результат работы DialogFragment в вызывающую Activity. Для этого создается дополнительный интерфейс. Activity реализует этот интерфейс, а у DialogFrament есть ссылка на Activity.
Если инициатором вызова DialogFragment является другой Fragment, то в таком подходе нам придется сначала отправить результат в Activity, а потом уже из Activity в заинтересованный в данных Fragment. Отсюда и минусы:
- Activity знает о деталях реализации фрагмента (сегодня это дополнительный диалог, а завтра можем реализовать все на одном фрагменте);
- дополнительный код (больше кода = больше возможностей для ошибок).
К счастью, у класса Fragment есть 3 метода, которые позволят реализовать передачу событий из одного фрагмента в другой фрагмент минуя Activity. Это:
- void setTargetFragment(Fragment fragment, int requestCode)
- Fragment getTargetFrament
- int getTargetRequestCode()
Суть метода
Вызывающий фрагмент:
- с помощью setTargetFragment устанавливает себя в качестве targetFrament и устанавливает requestCode;
- реализует метод onActivityResult в котором обрабатывает requestCode и resultCode, а также имеет доступ к дополнительным данным через intent.
Вызываемый фрагмент:
- c помощью getTargetFrament получает ссылку на вызывающий фрагмент;
- с помощью getTargetRequestCode получает код с которым он был вызыван;
- вызывает onActivityResult вызывающего фрагмента и передает результаты своего выполнения через resultCode и Intent (для дополнительных).
Ниже пример кода. Для простоты оставил только актуальные для статьи части.
Заключение
Перечисленные в начале статьи минусы в таком подходе отсутствуют. Только два взаимодействующих элемента знают друг о друге. Для остальных это уже неизвестные детали реализации.
Мне бы хотелось узнать, о таком варианте реализации еще при изучении официального гида по диалогам. Я же узнал позже благодаря StackOverflow.
Источник
Как правильно закрыть DialogFragment?
Документы говорят это для dismiss() метод из Dialog учебный класс:
Закрыть этот диалог, убрав его с экрана. Этот метод может быть безопасно вызван из любого потока. Обратите внимание, что вы не должны переопределять этот метод для очистки после закрытия диалогового окна, вместо этого реализуйте это в onStop() ,
В моем коде все, что я делаю, это звоню getDialog().dismiss() отклонить это. Но я больше ничего не делаю и даже не использую onStop() , Поэтому я спрашиваю, как именно уволить DialogFragment чтобы избежать утечек памяти и т.д..
5 ответов
tl;dr: правильный способ закрыть DialogFragment это использовать dismiss() прямо на DialogFragment.
Управление диалогом (решение о том, когда показывать, скрывать, отклонять его) должно осуществляться через API, а не через прямые вызовы в диалоге.
Таким образом, вы не должны использовать getDialog().dismiss() , так как это вызвало бы dismiss() в диалоге. Вместо этого вы должны использовать dismiss() метод самого DialogFragment:
публичная недействительность ()
Отклонить фрагмент и его диалог. Если фрагмент был добавлен в задний стек, все заднее состояние стека вплоть до этой записи будет включено. В противном случае будет совершена новая транзакция для удаления фрагмента.
Как вы можете видеть, это заботится не только о закрытии диалога, но и об обработке фрагментных транзакций, вовлеченных в процесс.
Вам нужно только использовать onStop если вы явно создали какие-либо ресурсы, требующие ручной очистки (закрытие файлов, закрытие курсоров и т. д.). Даже тогда я бы переопределил onStop ДиалогФрагмент, а не onStop основного диалога.
Источник
Свежий взгляд на отображение диалогов в Android
На картинке первая мысль читателя, который недоумевает, что можно написать про такую простую задачу как отображения диалога. Аналогично думает и менеджер: «Тут ничего сложного, наш Вася за 5 минут сделает». Я, конечно, утрирую, но на самом деле всё не так просто, как кажется на первый взгляд. Особенно если мы говорим про Android.
Итак, на дворе шёл 2019 год, а мы всё ещё не умеем нормально показывать диалоги.
Давайте всё по порядку, и начнем с постановки задачи:
Требуется показать простой диалог с текстом для подтверждения действия и кнопками «подтвердить/отмена». По нажатию на кнопку «подтвердить» — совершить действие, по кнопке «отмена» — закрыть диалог.
Решение «в лоб»
Я бы назвал этот способ джуниорским, потому что не первый раз сталкиваюсь с непониманием, почему нельзя просто использовать AlertDialog, как показано ниже:
Довольно распространенный способ для начинающего разработчика, он очевиден и интуитивно понятен. Но, как и во многих случаях при работе с Android, этот способ совершенно неправильный. На ровном месте мы получаем утечку памяти, достаточно повернуть устройство, и вы увидете в логах такую ошибку:
На Stackoverflow вопрос по этой проблеме один из самых популярных. Если коротко, то проблема в том, что мы либо показываем диалог, либо не закрываем диалог после завершения работы активити.
Можно, конечно, вызывать dismiss у диалога в onPause или onDestroy активити, как советуют в ответе по ссылке. Но это не совсем то, что нам нужно. Мы хотим, чтобы диалог восстанавливался после поворота устройства.
Устаревший способ
До появления фрагментов в Android диалоги должны были отображаться через вызов метода активити showDialog. В этом случае активити правильно управляет жизненным циклом диалога и восстанавливает его после поворота. Создание самого диалога нужно было реализовать в коллбэке onCreateDialog:
Не очень удобно, что приходится заводить идентификатор диалога и передавать параметры через Bundle. И мы все ещё можем получить проблему «leaked window», если попытаемся отобразить диалог после вызова onDestroy у активити. Такое возможно, например, при попытке показать ошибку после асинхронной операции.
Вообще, эта проблема типична для Android, когда нужно что-то сделать после асинхронной операции, а активити или фрагмент уже уничтожен в этот момент. Наверное, поэтому MV*-паттерны более популярны в Android-сообществе, чем среди iOS-разработчиков.
Способ из документации
В Android Honeycomb появились фрагменты, и описанный выше способ устарел, а метод showDialog у активити помечен как deprecated. Нет, AlertDialog не устарел, как ошибаются многие. Просто теперь появился DialogFragment, который оборачивает объект диалога и управляет его жизненным циклом.
Родные фрагменты тоже устарели начиная с 28 API. Теперь следует использовать только реализацию из Support Library(AndroidX).
Давайте реализуем нашу задачу, как это предписывает официальная документация:
- Для начала нужно наследоваться от DialogFragment и реализовать создание диалога в методе onCreateDialog.
- Описать интерфейс событий диалога и инстанцировать слушатель в методе onAttach.
- Реализовать интерфейс событий диалога в активити или фрагменте.
Если читателю не очень понятно, почему нельзя передавать слушатель через конструктор, то он может почитать подробнее об этом тут
Код фрагмента диалога:
Достаточно много кода получилось, не так ли?
Как правило, в проекте есть какой-нибудь MVP, но я решил, что вызовы презентера можно опустить в данном случае. В примере выше стоит ещё добавить статический метод создания диалога newInstance и передачу параметров в аргументы фрагмента, всё как полагается.
И это всё ради того, чтобы диалог вовремя скрывался и правильно восстанавливался. Не удивительно, что появляются такие вопросы на Stackoverflow: один и два.
Поиск идеального решения
Текущее положение дел нас не устраивало, и мы стали искать способ, как сделать работу с диалогами более комфортной. Было ощущение, что можно сделать проще, почти как в первом способе.
Ниже сформулированы соображения, которыми мы руководствовались:
- Нужно ли сохранять и восстанавливать диалог после убийства процесса приложения?
В большинстве случаев это не требуется, как и в нашем примере, когда нужно показать простое сообщение или что-то спросить. Такой диалог актуален пока не потеряно внимание пользователя. Если его восстановить после долгого отсутствия в приложении, то пользователь потеряет контекст с планируемым действием. Поэтому нужно только поддержать повороты устройства и правильно обрабатывать жизненный цикл диалога. Иначе от неловкого движения устройства пользователь может потерять только что открытое сообщение, не прочитав его. - При использовании DialogFragment появляется слишком много boilerplate-кода, теряется простота. Поэтому было бы неплохо избавиться от фрагмента как обёртки и использовать Dialog напрямую. Для этого придется хранить состояние диалога, чтобы показать его вновь после пересоздания View и скрывать, когда View умирает.
- Все привыкли воспринимать показ диалога как команду, особенно если работаешь только с MVP. Задачу последующего восстановление состояния берет на себя FragmentManager. Но можно посмотреть на эту ситуацию иначе и начать воспринимать диалог как state. Это намного удобнее при работе с паттернами PM или MVVM.
- Учитывая, что большинство приложений сейчас используют реактивные подходы, появляется потребность в том, чтобы диалоги были реактивными. Основная задача — не разрывать цепочку, которая инициирует показ диалога, и привязать реактивный поток событий для получения результата от него. Это очень удобно на стороне PresentationModel/ViewModel, когда манипулируешь несколькими потоками данных.
Мы учли все вышеописанные требования и придумали способ реактивного показа диалогов, который успешно реализовали в нашей библиотеке RxPM (про нее есть отдельная статья).
Само решение не требует библиотеки и может быть сделано отдельно. Руководствуясь идеей «диалог как state» можно попробовать построить решение на основе модных ViewModel и LiveData. Но я оставлю это право за читателем, а далее речь пойдет уже о готовом решении из библиотеки.
Реактивный способ
Я покажу, как исходная задача решается в RxPM, но сначала пару слов о ключевых понятиях из библиотеки:
- PresentationModel — хранит реактивный стейт, содержит UI-логику, переживает повороты.
- State — реактивный стейт. Можно воспринимать как обертку над BehaviorRelay.
- Action — обертка над PublishRelay, служит для передачи событий от View в PresentationModel.
- State и Action имеют observable и consumer.
За состояние диалога отвечает класс DialogControl. Он имеет два параметра: первый для типа данных, которые должны отображаться в диалоге, второй — для типа результата. В нашем примере тип данных будет Unit, но это может быть сообщение пользователю или любой другой тип.
В DialogControl есть следующие методы:
- show(data: T) — просто отдает команду на отображение.
- showForResult(data: T): Maybe — показывает диалог и открывает поток для получения результата.
- sendResult(result: R) — отправляет результат, вызывается со стороны View.
- dismiss() — просто скрывает диалог.
В DialogControl хранится состояние — есть диалог на экране или нет (Displayed/Absent). Вот так это выглядит в коде класса:
Создадим простую PresentationModel:
Обратите внимание, что обработка кликов, получение подтверждения и обработка действия реализованы в одной цепочке. Это позволяет сделать код сфокусированным и не раскидывать логику по нескольким коллбэкам.
Далее просто привязываем DialogControl во View с помощью экстеншена bindTo.
Собираем обычный AlertDialog, а результат отправляем через sendResult:
При типичном сценарии под капотом происходит примерно следующее:
- Кликаем на кнопку, событие через Action «buttonClicks» попадает в PresentationModel.
- По этому событию запускаем отображение диалога через вызов showForResult.
- В результате состояние в DialogControl меняется с Absent на Displayed.
- При получении события Displayed — вызывается лямбда, которую мы передали в привязке bindTo. В ней создается объект диалога, который затем показывается.
- Пользователь нажимает, кнопку «Confirm», срабатывает слушатель и результат нажатия отправляется в DialogControl посредством вызова sendResult.
- Далее результат попадает во внутренний Action «result», а состояние с Displayed меняется на Absent.
- При получении события Absent текущий диалог закрывается.
- Событие от Action «result» попадает в поток, который был открыт вызовом showForResult и обрабатывается цепочкой в PresentationModel.
Стоит отметить, что диалог закрывается и в момент, когда View отвязывается от PresentationModel. В этом случае состояние остается Displayed. Оно будет получено при следующей привязке и диалог будет восстановлен.
Как видите, необходимость в DialogFragment пропала. Диалог показывается, когда View привязывается к PresentationModel и скрывается, когда View отвязывается. За счёт того, что состояние хранится в DialogControl, который в свою очередь хранится в PresentationModel, диалог восстанавливается после поворота устройства.
Пишите диалоги правильно
Мы с вами рассмотрели несколько способов отображения диалогов. Если вы все ещё показываете первым способом, то прошу вас, не делайте больше так. Для любителей MVP ничего не остается, как использовать стандартный способ, который описан в официальной документации. К сожалению, склонность к императивности этого паттерна не позволяет сделать по-другому. Ну, а фанатам RxJava рекомендую присмотреться к реактивному способу и нашей библиотеке RxPM.
Источник
Урок 110. Android 3. Fragments. DialogFragment — диалог
— работаем с DialogFragment
Продолжаем рассматривать наследников Fragment. DialogFragment – отличается от обычного фрагмента тем, что отображается как диалог и имеет соответствующие методы.
Построить диалог можно двумя способами: используя свой layout-файл и через AlertDialog.Builder. Нарисуем приложение, которое будет вызывать два диалога, построенных разными способами.
Project name: P1101_DialogFragment
Build Target: Android 4.1
Application name: DialogFragment
Package name: ru.startandroid.develop.p1101dialogfragment
Create Activity: MainActivity
Добавим строки в strings.xml:
Мы будем создавать два диалога, соответственно нам понадобятся два фрагмента.
Создадим layout-файл для первого фрагмента.
Так будет выглядеть наш диалог – текст сообщения и три кнопки.
Создаем класс Dialog1.java:
В onCreateView мы получаем объект Dialog с помощью метода getDialog и устанавливаем заголовок диалога. Далее мы создаем view из layout, находим в нем кнопки и ставим им текущий фрагмент в качестве обработчика.
В onClick выводим в лог текст нажатой кнопки и сами явно закрываем диалог методом dismiss.
Метод onDismiss срабатывает, когда диалог закрывается. Пишем об этом в лог.
Метод onCancel срабатывает, когда диалог отменяют кнопкой Назад. Пишем об этом в лог.
Создаем второй фрагмент. Здесь мы будем строить диалог с помощью билдера, поэтому layout-файл не понадобится. Создаем только класс Dialog2.java:
Обычно для заполнения фрагмента содержимым мы использовали метод onCreateView. Для создания диалога с помощью билдера используется onCreateDialog. Создаем диалог с заголовком, сообщением и тремя кнопками. Обработчиком для кнопок назначаем текущий фрагмент.
В onClick определяем, какая кнопка была нажата и выводим соответствующий текст в лог. В случае создания диалога через билдер, диалог сам закроется по нажатию на кнопку, метод dismiss здесь не нужен.
Методы onDismiss и onCancel – это закрытие и отмена диалога, аналогично первому фрагменту.
Меняем layout-файл для MainActivity — main.xml:
Здесь только две кнопки.
Создаем диалоги и запускаем их методом show, который на вход требует FragmentManager и строку-тэг. Транзакция и коммит происходят внутри этого метода, нам об этом думать не надо.
Все сохраняем и запускаем приложение.
Отобразился наш простенький диалог.
Жмем какую-нибудь кнопку, например, Yes — диалог закрылся. Смотрим логи:
Dialog 1: Yes
Dialog 1: onDismiss
Снова запустим первый диалог и нажмем клавишу Назад (Back). Смотрим лог:
Сработал onCancel – диалог был отменен, и onDismiss – диалог закрылся.
Если мы будем поворачивать экран, то каждый раз будет отрабатывать onDismiss, но диалог снова будет отображен после поворота.
Запустим второй диалог – нажмем кнопку Dialog 2.
Отобразился стандартный сконструированный нами диалог. Жмем, например, No – диалог закрылся. В логах:
Dialog 2: No
Dialog 2: onDismiss
Снова запустим второй диалог и нажмем Назад. В логах:
Все так же, как и в первом случае.
Еще несколько слов по теме.
Если вы не хотите, чтобы ваш диалог можно было закрыть кнопкой, используйте для вашего диалог-фрагмента метод setCancelable с параметром false.
Есть еще один вариант вызова диалога. Это метод show, но на вход он уже принимает не FragmentManager, а FragmentTransaction. В этом случае система также сама вызовет commit внутри show, но мы можем предварительно поместить в созданную нами транзакцию какие-либо еще операции или отправить ее в BackStack.
Вы можете использовать диалог-фрагменты, как обычные фрагменты и отображать их на Activity, а не в виде диалога. Но при этом будьте аккуратнее с использованием getDialog. Я так понял, что он возвращает null в этом случае.
Если AlertDialog.Builder вам незнаком, то посмотрите Урок 60 и несколько следующих за ним. Там достаточно подробно описано, как создавать различные диалоги.
На следующем уроке:
— работаем с PreferenceFragment
— используем Headers
Источник