My goal: I want to send push notification to users. When user tap on that notification, user should navigate to a specific fragment named HitoryDetailsFragment.
What I did: I implemented FirebaseInstanceIdService & FirebaseMessagingService and I'm able to catch Firebase message which is sent from a backend server. I implemented the deep link by an "implicit deeplink". I implemented the navigation using nested navigation graphs. History related screens are placed in a separate navigation graph, graph_history.xml.
What is the issue: When the application is open, everything works fine. I tap on the notification -> I get redirected to HistoryDetailFragment, and when I press the back button, it returns me to the HistoryListFragment. But when the app is closed (not in the recent apps tray), instead of HistoryDetailsFragment, the SplashFragment is opened, and after that I get redirected to the HomeFragment (which is correct, because that's how the flow should work). But, how can I redirect the user to the HistoryDetails, when the app is closed? Also, I should show a Pin screen, where the user should input his pin, if he did not before, before showing the HistoryDetailsFragment.
My code is like below.
private fun createPendingIntent(history: History?) = NavDeepLinkBuilder(this).run {
setGraph(R.navigation.graph_main)
history?.let {
//todo when the app is not open (not in tray of recent apps), this destination does not work
//this instead navigates to SplashFragment
//which is correct, because we have not entered the pin, so we are not entitled to open the history detail screen
//but how do I handle this case?
setDestination(if (it.type.isNews) R.id.nav_history_news else R.id.nav_history_detail)
setArguments(if (it.type.isNews) HistoryNewsFragmentArgs(it.link).toBundle() else HistoryDetailFragmentArgs(it.id).toBundle())
}
createPendingIntent()
}
The main nav graph:
History graph:
Related
I have multi module android App, so I have a question regarding back stack with deep link.
For example we can manage and clear back stack with setPopUpTo method in jetpack navigation.
Like home -> notification -> about
So If I need to back to home from about and clear notification screen between in back stack I can use in notification to about navigation:
val navOption = NavOptions.Builder().setPopUpTo(R.id.home_nav, false).build()
findNavController().navigate(dest,null, navOption)
So on back press I back to home directly and skip the notification route from back stack.
But in second case when I go from notification (feature home) to product detail (feature product) directly with the help of deep link like:
<fragment
android:id="#+id/productDetailFragment"
android:name="com.gymat.gym.domain.modules.courts.views.ProductDetailFragment"
android:label="productDetailFragment"
tools:layout="#layout/fragment_product_detail">
<argument
android:name="param_id"
android:defaultValue="default"/>
<deepLink
app:uri="myApp://productDetailFragment/{param_id}" />
</fragment>
And in notification I can route to product detail directly with the help of URI like this:
val uri = Uri.parse("myApp://productDetailFragment/2bb6e920-032c-4829-8801-cfe3fb787c36")
findNavController().navigate(uri)
In this way I can go to another graph child fragment directly with the help of deep link. So I stuck in a case when user come back from checkout to notification directly.
So here I have two cases:
first case when user go to checkout and press back the same product detail screen will show from back stack, but if user checout successsully I have to go back to notification screen directly from checkput. But in feature product we do not have access to other graph child fragment, So any idea about how I can set setPopUpTo to notification directly from checkout screen.
I come across requirement where I have to do certain task when user is coming via back navigation flow.
Suppose : Currently user is on A Screen and on button click it got navigated to B Screen.
On B Screen I'm using BackHandler to navigate user to back screen using navController.navigateUp() as this code working perfectly to navigate user to back screen.
But I'm unable to identify is user coming via back navigation or initial launch flow.
Already Tried Solution : val isFromBackNavigation = parentNavController.currentBackStackEntryAsState().value?.destination?.route == AppNav.Wishlist.route
This returns true for initial launch also.
What I want to achieve is that the app starts with a login page and after login the Main page should be show. From there other pages can be opened and normal navigation is allowed.
However, I do not want the users to navigate back to the login page. After the login the main page must be the root of the navigation.
I found lots of information on google on how to do it, but they all don't seem to work for me. Mainly I've been told to make my main page the root by setting MainPage directly to my PageMain that is also in my code now, but it does not works.
Other method should be to remove the login page from the navigation stack, but I can't get that to work. The samples I find compile but on runtime they crash my application saying either I cannot remove the current page or the page I am removing is not found.
Here is my code:
My app starts with PageLogin, for now it just has a button and when you click on it then it opens my PageMain
private void ButtonLogin_Clicked(object sender, EventArgs e)
{
// almost does what I want
Application.Current.MainPage = new PageMain();
// almost does what I want
// make PageMain the new main page, so you cannot go back to the login screen
//Application.Current.MainPage = new NavigationPage(new PageMain());
// error you cannot remove the page you are on
//var _navigation = Application.Current.MainPage.Navigation;
//var _lastPage = _navigation.NavigationStack.LastOrDefault();
////Remove last page
//_navigation.RemovePage(_lastPage);
////Go back
//_navigation.PopAsync();
// error page does not exists
//Application.Current.MainPage.Navigation.RemovePage(this);
//Navigation.PopAsync(); not supported on android
//Navigation.RemovePage(this); not supported on android
}
The MainPage is set in App.xaml.cs like this
public partial class App : Application
{
public App()
{
InitializeComponent();
//MainPage = new NavigationPage(new Pages.PageLogin());
MainPage = new Pages.PageLogin();
}
The code above opens my page PageMain so far so good.
Now when I click on the back button of the device, my app minimizes (or whatever it does on android to hide itself)
This is good because I don't want the user to go back to the login form
But, when I now click on recent apps button on the device, and click on my app to get it back in foreground, it moves back to the login form.
See this vid
How can I avoid that ?
EDIT
I tried setting IsTabStop to false, but also no result
public PageLogin()
{
InitializeComponent();
this.IsTabStop = false;
ButtonLogin.Clicked += ButtonLogin_Clicked;
}
This is a pure Android behavior and has nothing to do with Xamarin.Forms, when pressing the back button while your navigation stack of the app is empty, depending on which Android version is running, it will behave like follow:
Android 11 and lower: The system finishes the activity.
Android 12 and higher:
The system moves the activity and its task to the background instead of finishing the activity. This behavior matches the default system behavior when navigating out of an app using the Home button or gesture.
In most cases, this behavior means that users can more quickly resume your app from a warm state, instead of having to completely restart the app from a cold state...
Source: Back press behavior for root launcher activities.
In your case when you press the back button on the main screen, Android finishes the activity, if you want to confirm that, set a breakpoint on your AppShell.cs constructor or MainActivity.cs/OnCreate() you will notice that:
Home button pressed on main screen and restore back the app from android apps stack: none of the breakpoints will be hit because the app activity was conserved. In fact Android will call MainActivity.OnResume().
Back button press you will hit the breakpoints, because the activity was terminated and you are starting it over.
Some potential solutions
Save and keep an updated record of the logging state on a local DB (SQLite) or a file, at the app startup read this bool and accordingly show or no the login page (set the mainpage).
If you don't want your app to exit upon clicking back button, override OnBackPressed() in your MainActivity with an empty code:
public override void OnBackPressed()
{
}
Send your app to the back (pause it) rather than terminate it:
public override void OnBackPressed() => MoveTaskToBack(true);
More on OnBackPressed()
Related questions
What happens when back button or home or application removed from recent apps
How can you restrict/control the navigation routes the user can visit based on login status/role?
I worked out a method base on CFun's answer that seems to work fine during my tests.
I will show my implementation of this answer here, so it can be used by other people with the same problem.
And by doing so I also give a change to everybody to comment on this implementation of that answer.
The idea is to keep a reference to the prior page, everytime another page is opened. Then in the MainActivity.cs in the OnBackPressed method I can check if I am on the Root Page (which is my PageMain) or not.
When on the root page I can do MoveTaskToBack and otherwise I can set the current page to priorPage.
Here is the code:
What I did, first in my App.xaml.cs I put this static variable priorPage
public partial class App : Application
{
public static Page priorPage;
public App()
{
InitializeComponent();
MainPage = new Pages.PageLogin();
}
Then when my app starts (first page is the login page) and the user clicks on login, this code will be executed. The variable priorPage will be set to the page that currently is active, before opening a new page
private void ButtonLogin_Clicked(object sender, EventArgs e)
{
App.priorPage = this;
Application.Current.MainPage = new PageMain();
}
The same principle will be used for every page that is opened
private void ButtonSettings_Clicked(object sender, EventArgs e)
{
App.priorPage = this;
Application.Current.MainPage = new PageSettings();
}
And finally, in the MainActivity.cs I can now do this
public override void OnBackPressed()
{
if (App.Current.MainPage is PageMain)
{
MoveTaskToBack(true); // you are on the root, hide the app
}
else if (App.priorPage != null)
{
App.Current.MainPage = App.priorPage; // navigate back to you prior page
}
else
{
base.OnBackPressed();
}
}
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>
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.