Today I've started playing with Android's Navigation Component to implement authentication flow in my app. The idea is very simple: a user launches the app and it displays one piece of UI if she's authenticated and another if she's not.
What I've done is in my HomeFragment's onViewCreated method I check if a user is authenticated and if she's not I call the NavController's navigate() method by passing it the id of an action that will navigate to the AuthenticationFragment. Everything works fine until a user clicks the back button while in AuthenticationFragment, because then I get this error. I still have no idea what's the actual reason for that error, but the idea of navigation happening too quickly seems similar to my case: the destinations first switch from AuthenticationFragment to HomeFragment and then immediately from HomeFragment to AuthenticationFragment again, because HomeFragment finds out again that a user is not authenticated.
Despite of the error I'm getting, this approach still seems wrong to me. The user shouldn't be able to go back to the HomeFragment (and see the screen flicker when fragments are immediately switching back and forth) before she authenticates. I've also looked at this Android's official guide to implementing an authentication flow, but it too seems wrong to me, because a redundant third piece of UI is involved there (the MainFragment). I could replace it with a splash screen in my case, but it would still stay in the back stack and the user would be able to go back to splash screen, which is obviously nonsense.
So what would be the correct way to implement an authentication flow using the Android's new Navigation Component? The functionality I want is: if a user is not authenticated, then she's redirected to an authentication UI and if she presses the back button from there she should exit the application.
UPDATE: I know I could just listen to the back press event and close the application from there, but I still hope there's some elegant solution using Android's Navigation Component.
In your login fragment, you need to declare "Pop up-to" action from home fragment.
This pops all non-matching destinations from the back stack until
this destination is found.
"popUpToInclusive = true" pops the given destination from backstack
<fragment
android:id="#+id/loginFragment"
android:name="com.example.navigationsample.fragments.Login"
android:label="Login_Fragment"
tools:layout="#layout/layout_login">
<action
android:id="#+id/action_loginFragment_to_homeFragment"
app:destination="#id/homeFragment"
app:popUpTo="#id/loginFragment"
app:popUpToInclusive="true"/>
</fragment>
Related
The two screens relevant to my issue amount to MainFragment (a master view) and ProfileFragment (a detail view). The launch path from login -> main -> profile works fine. The return path isn't working as well. I get stranded at main when navigating backward with via gesture or device button.
On the initial "back" from the profile, I'm returned to main and see main's onViewCreated, onResume lifecycles. I expect back from main would return to login or even exit the app, but instead it reloads main a dozen times before crashing.
For any subsequent "back" from main, the logs reveal main's onAttach, onCreate, onViewCreated, onResume lifecycles recurring each time. This seems suspicious although it's a pretty vanilla navigation graph, a single Activity hosting four basic Fragments. They're all navigated with basic actions using their generated, type-safe Directions classes. I haven't overridden onBackPressed in any of these classes.
Combinations of app:popUpTo="#+id/loginFragment" and/or app:popUpToInclusive="true" haven't helped.
Do I need to implement back or "up" in order to work as expected? Any thoughts are appreciated.
On closer inspection, it's working the way it's coded. Of course it is!
The preceding LoginFragment checks a SharedPreference value and navigates to main. I confirmed that happens so quickly I thought I never returned to login screen, when in fact I did.
I'll investigate removing login screen from the stack after authentication.
In my navigation, I have something like this:
When I open registrationFragment (in entry_graph) from a deep-link, and finish registration, I am navigated to main_graph (MainFragment).
From there, I can access to menuFragment and click on logOut, which calls:
MainFragmentDirections.actionMainFragmentToEntryFragment()
It opens the registrationFragment instead of loginFragment. I do not expect that since loginFragment is the home fragment on that graph.
When I have similar navigation but not using deep-links, it works as it should, it opens the first fragment in that graph, but with this deep-link flow, it always opens the fragment that was opened by the deep-link, no mater where it is positioned in the navigation graph.
I have searched for solutions on SO and web, but could not find the same problem anywhere.
I have tried removing it manually from back-stack and similar solutions but I could not succeed, it always opens the fragment that was opened by the deep-link in first place.
I am using navigation version 2.1.0, but tested this flow on the currently latest 2.2.0 and it behaves the same.
Does anybody knows if there is a solution for this navigating flow?
How do navigate in step#2? I tried the same flow and it worked when I used the code described here.
Basically this is the code
PendingIntent pendingIntent = new NavDeepLinkBuilder(context)
.setGraph(R.navigation.nav_graph)
.setDestination(R.id.android)
.setArguments(args)
.createPendingIntent();
As described in the doc, when you navigate using intent, the back-stack is reset, and populated with the home fragment of each previous graph.
Just make sure the required back-stack actually described correctly in the nav_graph xml - each home fragment opens a new indent.
Hope it works for you :)
I have start authorization screen, I want the user to log in every time the application starts, even onResume
In onResume() of your Activity navigate to your authorization screen. We want to use a kind of navigation that can be used from any fragment, so the best option would be the navigate(deepLink) method.
Add a DeepLink to the AuthorizationFragment in your navigation.xml:
<deepLink
android:id="#+id/deepLink"
app:uri="http://www.example.com/authorization" />
In Activity onResume()
findNavController(R.id.container).navigate("http://www.example.com/authorization")
If the destination is reachable from current NavGraph, it will navigate there.
Dismiss the authorization screen simply by calling NavController.navigateUp() from the authorization screen.
See documentation:
Navigate to a destination with Navigation Component using URI
How to create an implicit deep link
When I open the app from a deeplink (user clicks on URL) and press back button I expect user to navigate to a previous fragment in my navigation graph but it just exits the app.
The documentation says that back navigation should work the same way as if it the user got to that screen naturally.
Can I somehow specify the desired backstack in my navigation graph? Or can be backstack formed automatically after a deeplink? For older version of the library I found out that after back press it should navigate to the root of my navigation graph but that does not happen.
I’m using the navigation library from Android architecture components (version 1.0.0-beta01).
I found out what's going on here, for explicit deep links its supposed to go to a new back stack which is what you app would have if a user had naturally navigated to the view not the existing back stack (existing stack is cleared.
When a user opens your app via an explicit deep link, the task back stack is cleared and replaced with the deep link destination. When nesting graphs, the start destination from each level of nesting—that is, the start destination from each element in the hierarchy—is also added to the stack. This means that when a user presses the Back button from a deep link destination, they navigate back up the navigation stack just as though they entered your app from its entry point.
For implicit its a bit strange. You can make it do what explicit does but setting Intent.FLAG_ACTIVITY_NEW_TASK otherwise the back button and the navigation up button do two separate things:
The back button will do as you might expect, it will go back in your apps existing back stack and load that fragment.
The up button however will clear the a back stack and make a new one as if it was an explicit link.
If the flag is not set, you remain on the task stack of the previous app where the implicit deep link was triggered. In this case, the Back button takes you back to the previous app, while the Up button starts your app's task on the hierarchical parent destination within your navigation graph.kquote
Source: Android Documentation
As describe here back button should return to the previous fragment, you can set it manually in Java like this: button.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.next_fragment, null));
In Kotlin like that: button.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.next_fragment, null))
The Android system maintains a back stack containing the last visited destination. The first destination of your app is placed on the stack when the user opens the app. Each call to the navigate() method puts another destination on the top of the stack. Conversely, pressing the Up or Back button calls the NavController.navigateUp() and NavController.popBackStack() methods, respectively, to pop the top destination off of the stack.
Make sure that you are using NavHostFragment and not <fragment> in your hosting fragment activity.
I've got an xamarin forms/prism app, and my hardware back button does nothing on the initial page.
If I navigate to another page, it closes the app as expected. If I navigate to the initial page again, it also closes the app - but not if the app just started.
Is there something I'm missing?
My class App mainly has an OnInitialized that navigates to the initial page:
protected override void OnInitialized()
{
NavigationService.NavigateAsync( "MyMasterDetail/MyNavigationPage/StartPage", animated: false );
}
On MyMasterDetail, there are buttons that navigate to MyNavigationPage/SettingsPage and other pages like that.
It doesn't matter if I use Android 5 in Emulator or Android 6 on a real device, the behaviour is the same.
When using a MasterDetail as your root, you are not actually navigating anywhere else. You are simply changing the Detail property of the MasterDetail to another Page. This is not a navigation action. So you are not really navigating. If you want to fake it, you need to add the INavigationPageOptions to your MyNavigationPage and set the ClearNavigationStackOnNavigation property to false. This will continuously push new pages onto the MasterDetailPage.Detail MyNavigationPage without clearing the stack (PopToRoot). Then this will allow your bac button to behave like you are wanting.