Эквивалент startActivityForResult () с навигацией по архитектуре Android

У меня рабочий процесс с 3 экранами. От «экрана 1» до доступа к «экрану 2» пользователь должен принять какие-то условия, которые я называю на моем изображении «модальными». Но он должен принять эти условия только один раз. В следующий раз, когда он будет на первом экране, он может перейти непосредственно к экрану 2. Пользователь может выбрать НЕ принимать условия, и поэтому мы возвращаемся к «экрану 1» и не пытаемся перейти к «экрану 2».

Рабочий процесс приложения

Мне интересно, как это сделать с помощью нового компонента навигации.

Раньше что бы я делал:

  • На экране 1 проверьте, должен ли пользователь принять условия
  • Если нет, начните действие "Экран 2".
  • Если да, используйте startActivityForResult() и дождитесь результата модального окна. Отметьте условия как принятые. Пуск "экран 2"

Но с навигационным графом нет возможности запустить фрагмент для получения результата.

Я мог бы отметить условия как принятые на «модальном» экране и запустить «экран 2» оттуда. Дело в том, что для доступа к экрану 2 мне нужно сделать сетевой запрос. Я не хочу дублировать вызов API и обработку его результата как в «экране 1», так и в «модальном».

Есть ли способ вернуться с «модального» режима на «экран 1» с некоторой информацией (пользователь принял условия) с помощью навигации Jetpack?

Изменить: в настоящее время я обхожу это, используя тот же алгоритм, который Яхья предлагает ниже: используя Activity только для модального окна и используя startActivityForResult из «экрана 1». Мне просто интересно, могу ли я продолжать использовать навигационный график для всего потока.


person Jonas Schmid    schedule 05.06.2018    source источник
comment
Год спустя, есть ли правильный способ его реализовать? Я попробовал принять ответ, но таких методов нет, api изменился? сериализованный обратный вызов - очень плохое решение   -  person Pavel Poley    schedule 21.04.2020


Ответы (6)


В версии 1.3.0-alpha04 фрагмента AndroidX библиотека они представили новые API, которые позволяют передавать данные между Fragments.

Добавлена ​​поддержка передачи результатов между двумя фрагментами через новые API-интерфейсы FragmentManager. Это работает для фрагментов иерархии (родительский / дочерний), DialogFragments и фрагментов в Navigation и гарантирует, что результаты будут отправлены только на ваш Fragment, пока он хотя бы STARTED. (b / 149787344)

FragmentManager получил два новых метода:

  • _3 _, который можно рассматривать аналогично существующему Activity#setResult;
  • _ 5_, который позволяет вам прослушивать / наблюдать изменения результатов.

Как это использовать?

В FragmentA добавьте FragmentResultListener к FragmentManager в методе onCreate:

setFragmentResultListener("request_key") { requestKey: String, bundle: Bundle ->
    val result = bundle.getString("your_data_key")
    // do something with the result
}

В FragmentB добавьте этот код, чтобы вернуть результат:

val result = Bundle().apply {
    putString("your_data_key", "Hello!")
}
setFragmentResult("request_key", result)

Начать FragmentB, например: с помощью:

findNavController().navigate(NavGraphDirections.yourActionToFragmentB())

Чтобы закрыть / закончить FragmentB позвоните:

findNavController().navigateUp()

Теперь ваш FragmentResultListener должен быть уведомлен, и вы должны получить свой результат.

(Я использую fragment-ktx, чтобы упростить приведенный выше код)

person Ziem    schedule 19.05.2020
comment
Почему я больше не вижу setFragmentResultListener в FragmentManager, но он существует в документации: developer.android.com/reference/kotlin/androidx/fragment/app/? Я пробовал как v2.2.2, так и v2.3.0-rc01. - person android developer; 23.06.2020
comment
@androiddeveloper setFragmentResultListener существует в артефакте фрагмент-ktx. Если вы его не используете, вы можете попытаться вызвать fragmentManager.setFragmentResultListener() внутри своего фрагмента. - person Ziem; 24.06.2020
comment
Кажется, я запутался между androidx.navigation:navigation-fragment-ktx и тем, что вы на самом деле имели в виду, а именно androidx.fragment:fragment-ktx. Кажется, сейчас работает. Но у вас есть некоторые ошибки: вам нужно использовать не только fragmentManager (который устарел). Вы должны использовать parentFragmentManager. Кроме того, внутри второго фрагмента вы не можете преобразовать его в Activity. Вы должны использовать findNavController().navigateUp() напрямую. Я также заметил, что обратный вызов происходит сразу, когда я устанавливаю результат, а не когда я возвращаюсь к первому фрагменту. Почему? - person android developer; 24.06.2020
comment
Да, в обоих случаях вы правы. parentFragmentManager следует использовать, поскольку fragmentManager устарел (сейчас я не могу редактировать свой комментарий). Но я отредактировал свой ответ и удалил приведение к Activity, как вы предложили. Использование findNavController() - правильный способ сделать это. - person Ziem; 24.06.2020
comment
В документации для Fragment.setFragmentResultListener говорится: Once this Fragment is at least in the androidx.lifecycle.Lifecycle.State.STARTED state, any results set by setFragmentResult using the same requestKey will be delivered to the FragmentResultListener.onFragmentResult callback., и я могу подтвердить это поведение. - person Ziem; 24.06.2020
comment
Но как он мог быть в состоянии НАЧАЛО, если текущий Фрагмент - второй? Попробуйте, например, установить значение, но отложить вызов findNavController().navigateUp() (или не вызывать его вообще). И используйте журналы для печати при вызове обратного вызова. - person android developer; 24.06.2020
comment
Он запускает onFragmentResult обратный вызов, когда я возвращаюсь к первому Fragment, и это происходит после onStart этого первого Fragment. Вы начинаете с FragmentA - ›FragmentB -› setFragmentResult - ›нажимаете назад -› FragmentB закрывается - ›FragmentA onStart вызывается -› и затем onFragmentResult запускается - person Ziem; 24.06.2020

Есть несколько альтернатив модели общего представления.

  1. fun navigateBackWithResult (result: Bundle), как описано здесь https://medium.com/google-developer-experts/using-navigation-architecture-component-in-a-large-banking-app-ac84936a42c2

  2. Создайте обратный звонок.

ResultCallback.kt

interface ResultCallback : Serializable {
    fun setResult(result: Result)
}

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

<argument android:name="callback"
                  app:argType="com.yourpackage.to.ResultCallback"/>

Сделайте фрагмент A реализацией ResultCallback, фрагмент B получит аргументы и передаст данные обратно через них, args.callback.setResult (x)

person AntPachon    schedule 16.04.2019
comment
Спасибо! Эта статья - чистое золото. - person Andre Romano; 20.05.2019
comment
Спасибо за прекрасную ссылку! - person Adam; 03.10.2019
comment
У меня это решение не работает. Когда я перехожу от фрагмента A (вызывающий) к фрагменту B (получатель). Экземпляр FragmentA уничтожается. FragmentB ссылается на старый мертвый FragmentA. - person Pablo Valdes; 24.10.2019
comment
Это работает, но насколько это хорошая практика? Я не смог найти никаких рекомендаций Google по этому поводу - person G_comp; 05.11.2019
comment
У меня это отлично сработало, и я подумал, что это лучшее решение, пока приложение не было фоновым при работе с целевым фрагментом, и оно не вылетело из-за NotSerializableException. - person Pilot_51; 19.11.2019
comment
Вы должны быть предельно осторожны с подходом Serializable callback - очень легко случайно сделать ваш обратный вызов не Serializable, и вы будете предупреждены только во время выполнения, когда ваш компонент будет воссоздан. Это подробно объясняется здесь: medium.com/@lukeneedham/ - person Luke; 30.10.2020

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

person LaVepe    schedule 08.08.2018
comment
Правильно. Google подтвердил эту ошибку Issuesetracker.google.com/issues/79672220 - person Jonas Schmid; 08.08.2018

Недавно (в androidx-navigation-2.3.0-alpha02) Google выпустил правильный способ достижения такого поведения с помощью фрагментов.

Вкратце: (из примечания к выпуску)

Если Fragment A нужен результат от Fragment B ..

A должен получить saveStateHandle из currentBackStackEntry, вызвать getLiveData с ключом и посмотреть результат.

findNavController().currentBackStackEntry?.savedStateHandle?.getLiveData<Type>("key")?.observe(
    viewLifecycleOwner) {result ->
    // Do something with the result.
}

B должен получить savedStateHandle из previousBackStackEntry и установить результат с тем же ключом, что и LiveData в A

findNavController().previousBackStackEntry?.savedStateHandle?.set("key", result)

Связанная документация

person Juis Kel    schedule 19.02.2020
comment
Я получаю null в моем текстовом представлении внутри наблюдателя фрагмента A. - person Sagar Maiyad; 31.03.2020
comment
Что, если у меня есть Acitivity A , которому нужен результат Activity B? Я считаю, что это не сработает, поскольку navController в этих двух действиях различается? - person David Seroussi; 07.04.2020
comment
Дэвид, для активности воспользуемся старомодным способом - механизм startActivityForResult - person Juis Kel; 09.04.2020
comment
это все еще работает? я не могу получить доступ к currentBackStackEntry и savedStateHandle - person Pavel Poley; 21.04.2020
comment
на startActivityForResult у нас есть идентификатор запроса. Что мы имеем здесь? Не похоже, что таким образом мы можем различать запросы фрагментов. А Type - это тип значения ключа, верно? Но что, если мы обработаем несколько фрагментов и начнем искать результат? Есть ли какой-нибудь рабочий образец Github, в котором используется этот код? Кроме того, я пытался использовать это, и, похоже, обратный вызов вызывается дважды: один раз, когда он установлен, и другой раз, когда вы возвращаетесь к первому фрагменту. Почему? - person android developer; 23.06.2020

Есть еще один альтернативный обходной путь. Вы можете использовать другое действие навигации из модального окна обратно на screen1 вместо использования popBackStack(). В этом действии вы можете отправить любые данные, которые захотите, чтобы просмотреть их. Используйте эту стратегию, чтобы убедиться, что модальный экран не сохраняется в стеке навигации назад: https://stackoverflow.com/a/54015319/4672107.

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

person Carson Holzheimer    schedule 22.05.2019

Чтобы получить фрагмент вызывающего абонента, используйте что-то вроде fragmentManager.putFragment(args, TargetFragment.EXTRA_CALLER, this), а затем в целевом фрагменте получите фрагмент вызывающего абонента, используя

if (args.containsKey(EXTRA_CALLER)) {
    caller = fragmentManager?.getFragment(args, EXTRA_CALLER)
    if (caller != null) {
        if (caller is ResultCallback) {
            this.callback = caller
        }
    }
}
person Pnemonic    schedule 24.11.2019