Компонент архитектуры навигации - экран входа в систему

Я планирую реализовать такую ​​навигацию:
введите описание изображения здесь
Я столкнулся с проблемой когда пользователь находится в LoginFragmennt и нажимает кнопку "Назад", он снова загружает LognFragment, т.е. застрял в петле.

Я перехожу к LoginnFragment, используя условную навигацию согласно этому ответ.

Как правильно это реализовать?


person Yaswant Narayan    schedule 29.07.2018    source источник
comment
Вы застряли в цикле, потому что при нажатии назад вы возвращаетесь к предыдущему фрагменту, но поскольку то же условие все еще возникает (например, пользователь не вошел в систему), вы переходите к LoginFragment. Каков ваш сценарий? куда вы хотите перемещаться при нажатии назад?   -  person Alex    schedule 30.07.2018
comment
Я хочу закрыть приложение, когда пользователь нажимает кнопку "Назад"   -  person Yaswant Narayan    schedule 30.07.2018
comment
Как и в других приложениях, в первый раз, когда вы встречаетесь с экраном входа в систему и нажимаете кнопку «Назад», приложение закрывается.   -  person Yaswant Narayan    schedule 30.07.2018
comment
Попробуйте это решение stackoverflow.com/a/51589910/1268507   -  person Alex    schedule 30.07.2018
comment
В официальной документации есть сообщение об этой проблеме developer.android.com/guide/navigation/navigation- условно   -  person RonaldPaguay    schedule 15.06.2020


Ответы (3)


Вот официальное решение, предложенное Яном Лейком в видео по навигации от 23 июля 2020 г. на Android Developers канал YouTube. Решение основано на версии 2.3, которая представила возможность вернуть результат в предыдущий пункт назначения.

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

class LoginFragment : Fragment(R.layout.login) {
    ...

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        val navController = findNavController()
        val savedStateHandle = navController.previousBackStackEntry?.savedStateHandle
            ?: throw IllegalStateException("the login fragment must not be a start destination")
            
        savedStateHandle.set(LOGIN_SUCCESSFUL, false)
        // Hook up your UI, ask for login
        
        userRepository.addLoginSuccessListener {
            savedStateHandle.set(LOGIN_SUCCESSFUL, true)
            navController.popBackStack()
        } 
    }
}

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

class ProfileFragment : Fragment(R.layout.profile) {
    ...
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        val navController = findNavController()
        viewLifecycleOwner.lifecycleScope.launchWhenStarted {
            userRepository.userFlow.collect { user -> 
                if (user == null) {
                    navController.navigate(R.id.login)
                }
            }
        }
        
        val savedStateHandle = navController.currentBackStackEntry?.savedStateHandle
            ?: throw IllegalStateException()
        
        savedStateHandle.getLiveData<Boolean>(LOGIN_SUCCESSFUL)
            .observe(viewLifecycleOwner) { success -> 
                if (!success) {
                    // do whathever we want, just for an example go to
                    // the start destination which doesn't require login
                    val startDestination = navController.graph.startDestination
                    navController.navigate(startDestination, navOptions {
                        popUpTo(startDestination {
                            inclusive = true
                        })
                    })
                }
            }
    }
}
person Valeriy Katkov    schedule 04.11.2020

ИМХО, как я это делаю в своем приложении, немного чище. Просто добавьте эти настройки в навигационный график:

<fragment
    android:id="@+id/profile_dest"
    android:name="com.example.ProfileFragment">
    <action
        android:id="@+id/action_profile_dest_to_login_dest"
        app:destination="@id/login_dest"
        app:popUpTo="@+id/profile_dest"
        app:popUpToInclusive="true" />       
</fragment>

а затем перейдите к логину через

findNavController().navigate(R.id.action_profile_dest_to_login_dest).

popUpTo и popUpToInclusive закрываются ProfileFragment, когда мы переходим к LoginFragment, поэтому, если пользователь возвращается назад, он выходит из приложения.

person Carson Holzheimer    schedule 03.01.2019
comment
Как же тогда при успешном входе в систему вернуться к ProfileFragment? Должен ли login_dest также иметь действие для перехода к profile_dest, и мы вызываем Navigate? - person sbearben; 01.02.2019
comment
Да, точно. Как это показано на диаграмме в вопросе. Это действие также должно закрыть (удалить из backstack) экран входа в систему. - person Carson Holzheimer; 01.02.2019
comment
Таким образом, я реализовал свою навигацию таким образом, и, похоже, она работала, однако теперь, когда я перехожу к условному экрану (в моем случае вход в систему) и после успешного входа в систему, возвращаюсь назад (скажем, в ProfileFragment, как в исходном сообщении) и поворачиваю экран, приложение вылетает с ошибкой: IllegalStateException("unknown destination during restore: id/profile_dest. Не уверен, почему это могло произойти. - person sbearben; 02.02.2019
comment
Какой код вы использовали для возврата? Вы должны выполнить тот же процесс с popUpTo="@+id/login_dest" и popUpToInclusive="true". Не используйте popBackStack() - person Carson Holzheimer; 02.02.2019
comment
Вы, наверное, уже это сделали. FWIW Я не видел этой проблемы. Убедитесь, что у вас последняя версия навигационной системы, опубликуйте еще вопросы с вашим кодом, чтобы мы могли посмотреть, и добавьте проблему в Googles Issueetracker, если вы считаете, что это ошибка. - person Carson Holzheimer; 02.02.2019
comment
Спасибо за вашу помощь, и да, я использую навигацию, а не popBackStack (), чтобы вернуться назад. Я разместил здесь отдельный вопрос: stackoverflow.com/questions/54496008/ - person sbearben; 02.02.2019
comment
Эта проблема исправлена ​​ в версии 1.0.0-beta01. видимо - person Carson Holzheimer; 08.02.2019

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

override fun onBackPressed() {
    val currentDestination=NavHostFragment.findNavController(nav_host_fragment).currentDestination
    when(currentDestination.id) {
        R.id.loginFragment -> {
            finish()
        }
    }
    super.onBackPressed()
}
person Alex    schedule 30.07.2018
comment
Это решение работает. Теперь у меня есть сомнения. Для закрытия активности я бы использовал exit(0). В чем разница между exit(0) и finish()? - person Yaswant Narayan; 30.07.2018
comment
exit (0) убьет ваше приложение, finish () завершит вашу деятельность. Не используйте exit (0). - person Alex; 30.07.2018