Cannot navigate to page which was called once before - xamarin

Short intro into my application's-logic:
My application is using a REST-Api and needs a token for all requests but a specific one (let's call it "pairing-page").
Before I navigate to any of my pages I'm checking the target location. If my target is the page where I'm requesting the token, no checking for a existing token is made.
For every other page a token is necessary. If no token exists the user should navigate back to the "pairing-page".
My start-Page is my paring-page.
What's the problem?:
When starting the app (pairing-page) and getting the token and navigating to the 2nd page - let's call it "overview" - everything is ok. The overview has a button which calls a method that removes my token and navigates back to the pairing-page. That's working as well.
Now the interesting part:
When I try to pair & navigate to the overview again (2nd time), no navigation is done. By this I mean my overview is not popping up.
I'm using Xamarin's Shell Navigation and overriding the OnNavigating-Method.
Code:
protected override void OnNavigating(ShellNavigatingEventArgs args)
{
// check if BearerToken exist. If so, navigate to Overview
if (args.Target.Location.ToString() != "//Admission.Event.Pair"
&& args.Target.Location.ToString() != "Admission.Event.Pair")
{
App app = App.Current as App;
string bearerToken = RestServiceCalls.GetBearerToken(app._dependencyService);
// if a the token is missing call the Pairing-Page
if (string.IsNullOrEmpty(bearerToken)
|| string.IsNullOrWhiteSpace(bearerToken))
{
Device.BeginInvokeOnMainThread(async () => { await NavigateToPageAsync("Admission.Event.Pair"); });
return;
}
}
base.OnNavigating(args);
}
async Task NavigateToPageAsync(object pageName, bool animate = true)
{
await Xamarin.Forms.Shell.Current.GoToAsync($"{pageName.ToString()}", animate);
Xamarin.Forms.Shell.Current.FlyoutIsPresented = false;
}
Let's take a look into the args:
Application start, pairing-page:
args.Target.Location: //Admission.Event.Pair
args.Current.Location: null
The Pairing-Page is popping up, everything is ok.
Click on the Button which processes getting a token and after that navigating to the overview:
args.Target.Location: Admission.Event
args.Current.Location: //Admission.Event.Pair
The overview page is popping up, everything is ok. Got a valid token as well.
Click on Button which processes removing the token and navigating to the pairing-page:
args.Target.Location: Admission.Event.Pair
args.Current.Location: //Admission.Event.Pair/Admission.Event
The token is removed correctly (is null) and the pairing-page pops up. Everything is ok.
Now the problem occurs:
When I'm now trying to get a token and navigate to the overview again my args have this content and my overview wont show up:
args.Target.Location: Admission.Event
args.Current.Location: //Admission.Event.Pair/Admission.Event/Admission.Event.Pair
Any idea about that?
My first thought was it might be an issue with the target-location.

Like I said in my comment:
For me I could simple use:
if(shell != null){
INavigation navigation = shell.Navigation;
navigation.PopToRootAsync();
}

Related

nativescript-phone prevents Nativescript-contacts from returning

I have an app where I want to select a person from contacts and then send a text to that person. It works as expected for the first user, but after that the app never receives control after the contact is selected. I've isolated the problem to the Nativescript-phone plugin. If you simply call phone.sms() to send a text, and then call contacts.getContact(), the problem occurs. I see this on both Android and iOS.
I've created a sample app that demos the problem at https://github.com/dlcole/contactTester. The sample app is Android only. I've spent a couple days on this and welcome any insights.
Edit 4/21/2020:
I've spent more time on this and can see what's happening. Both plugins have the same event handler and same request codes:
nativescript-phone:
var SEND_SMS = 1001;
activity.onActivityResult = function(requestCode, resultCode, data) {
nativescript-contacts:
var PICK_CONTACT = 1001;
appModule.android.on("activityResult", function(eventData) {
What happens is that after invoking phone.sms, calling contacts.getContact causes control to return to the phone plugin, and NOT the contacts plugin. I tried changing phone's request code to 1002 but had the same results.
So, the next step is to determine how to avoid the collision of the event handlers.
Instead of using activityResult event, nativescript-phone plugin overwrites the default activity result callback.
A workaround is to set the callback to it's original value after you are done with nativescript-phone.
exports.sendText = function (args) {
console.log("entering sendText");
const activity = appModule.android.foregroundActivity || appModule.android.startActivity;
const onActivityResult = activity.onActivityResult;
permissions.requestPermissions([android.Manifest.permission.CALL_PHONE],
"Permission needed to send text")
.then(() => {
console.log("permission granted");
phone.sms()
.then((result) => {
console.log(JSON.stringify(result, null, 4));
activity.onActivityResult = onActivityResult;
})
})
}

How to extract state parameter from OpenIdConnect Token response in .Net Core MVC

We are using Azure b2c to handle our logins on our .net core MVC site.
We would like to use the optional state parameter to hold onto some data/a value between the initial request to the site (this value would likely be in a querystring param) which is then sent off to b2c to login, and the successfully logged in return back to the site.
OpenIDConnect allow the setting of this state value in the request, and will pass it back with the token response.
It appears that setting the value is relatively simple; in the OnRedirectToIdentityProvider event in the OpenIdConnectOptions like so:
public Task OnRedirectToIdentityProvider(RedirectContext context){
...
context.ProtocolMessage.SetParameter("state", "mystatevalue");
...
}
however, I cannot see how to get this value back again when the user is returned.
I can see that the OnTicketReceived event is hit, and this has a TicketReceivedContext which has a Form property with a state value in it, however this is still encrypted.
Where would i be able to get the un-encrypted value back from?
I have had a look at the Azure docs for b2c but I cannot find an example on this.
thanks
Managed to get this working by using the OnTokenValidated event.
This is able to get the unencrypted parameter as below.
...//first set up the new event
options.Events = new OpenIdConnectEvents()
{
...
OnTokenValidated = OnTokenValidated
};
...
private Task OnTokenValidated(TokenValidatedContext tokenValidatedContext)
{
var stateValue = tokenValidatedContext.ProtocolMessage.GetParameter("state");
if (stateValue != null)
{
//do something with that value..
}
return Task.CompletedTask;
}

Facebook SDK inside iOS app extension cannot find logged in user?

I've created a sample app Share extension. Followed the Apple guides, which means my project consists of a main app and a "share extension" target.
I've setup my Facebook SDK inside the main app, since the app's settings has some FB login/status functionality. It works well according to expectation: users can login and do some shares.
But I also want the logged-in user to be available to the extension target itself. When my extension comes up (in any app), I check Facebook's login status in viewDidLoad:, and it outputs "not logged in":
if ([FBSDKAccessToken currentAccessToken]) {
NSLog(#"logged in");
} else {
NSLog(#"not logged in");
}
The same code outputs "logged in" if called from within the main app. I suspect it has something to do with the fact that the extension target has a different bundle ID that looks like this: .suffix and I guess FB SDK is trying to read the user ID off the keychain cache, but maybe it's reading it off the wrong keychain due to the different bundle IDs... But it could be other reasons as well I guess.
Any idea how to keep Facebook SDK "logged in" inside the extension after the login itself occurred in the containing main app?
I have the exact same problem, as I develop a Message Extension App with a Facebook Login.
To solve the problem, I put the Login Button in the App and use the shared UserDefaults (with a group name) to store a property :
/*
* Get / sets if is FB connected (because we can't use FB SDK in Extension)
*/
public var isFacebookConnected : Bool {
get {
// Getting Bool from Shared UserDefaults
let defaults : NSUserDefaults = NSUserDefaults.init(suiteName: "MY_GROUP_NAME")!
let isFBConnected : Bool? = defaults.boolForKey("IsFacebookConnected")
if isFBConnected == nil {
return false
} else {
return isFBConnected!
}
}
set {
// Setting in UserDefaults
let defaults : NSUserDefaults = NSUserDefaults.init(suiteName: "MY_GROUP_NAME")!
defaults.setBool(newValue, forKey: "IsFacebookConnected")
defaults.synchronize()
}
}
This property is accessed from the App or from the Extension.
Then, in my Login / Logout methods I add :
//FBLogin
func loginButton(loginButton: FBSDKLoginButton!, didCompleteWithResult result: FBSDKLoginManagerLoginResult!, error: NSError!) {
if let error = error {
print(error.localizedDescription)
return
}
if result.isCancelled
{
return
}
// Set in Shared UserDefaults if connected / not connected
ConfigManager.sharedInstance.isFacebookConnected = true
...
}
func loginButtonDidLogOut(loginButton: FBSDKLoginButton!) {
// OK, keeping track
ConfigManager.sharedInstance.isFacebookConnected = false
}
Then, I keep track of my flag when login / logout.
Warning : this method is not bullet proof as I can't know if Facebook has been uninstalled, but it is robust enough for me.
I believe you'll need to take advantage of Keychain sharing capability in the app and extension to solve this.
https://developer.apple.com/documentation/security/keychain_services/keychain_items/sharing_access_to_keychain_items_among_a_collection_of_apps

What is the best solution for "reseting" the Authentication stack?

Currently in my application I have two Navigation stacks.
Authentication
Main
My Authentication stack looks like this:
Splash Page
Choose Create or Login Page
Login Page
After that I call:
CoreMethods.SwitchOutRootNavigation(NavigationContext.Main);
This all works fine.
When I call Logout from within the Main stack like this:
CoreMethods.SwitchOutRootNavigation(NavigationContext.Authentication);
I will currently be on "Login Page", but I really want it to be the first page "Splash Page".
Having the Navigation stacks remember the stack history is perfect for all other cases.
Question: What is the best solution for "reseting" the Authentication stack?
What I normally do in my apps is following.
I have IAuthenticationService which has a State property, which can be LoggedIn or LoggedOut. When session state changed due to explicit login, or for instance token expires, I set the State to LoggedOut. Also I fire a broadcast message SessionStateChanged through Messenger, so I can catch this message all around the app, and react correspondingly in UI level, like change screen states and so on.
If need to completely log the user, I mean show login page when State is LoggedOut, which is your case, I do the following. I use Xamarin.Forms, but the approach would be similar if you use native iOS or Android.
In my main App class (the one which derives from Xamarin.Forms.Application) I create a method call UpdateMainPage, something like this
private async void UpdateMainPage()
{
if (_authService.State == SessionState.LoggedIn)
MainPage = new NavigationPage(new RequestPage());
else
MainPage = new NavigationPage(new SignInPage());
}
What happens I just change the root page of the application to SignIn flow or Main flow depending on SessionState. Then in my constructor I do the following.
public FormsApp()
{
InitializeComponent();
_authService = Mvx.Resolve<IAuthenticationService>();
UpdateMainPage();
var messenger = Mvx.Resolve<IMvxMessenger>();
_sessionStateChangedToken = messenger.Subscribe<SessionStateChangedMessage>(HandleSessionStateChanged);
}
What I need to do, I need to setup main page beforehand, then I subscribe to SessionStateChanged event, where I trigger UpdateMainPage
private void HandleSessionStateChanged(SessionStateChangedMessage sessionStateChangedMessage)
{
UpdateMainPage();
}
I used this approach for several apps, and it work perfect for me. Hope this helps
I had the very same problem recently and this is what I did:
Navigation stacks:
public enum NavigationStacks {Authentication, Main}
In the App.xaml.cs:
//Navigation stack when user is authenticated.
var mainPage = FreshPageModelResolver.ResolvePageModel<MainPageModel>();
var mainNavigation = new FreshNavigationContainer(MainPage, NavigationStacks.Main.ToString());
//Navigation stack for when user is not authenticated.
var splashScreenPage= FreshPageModelResolver.ResolvePageModel<SplashScreenPageModel>();
var authenticationNavigation = new FreshNavigationContainer(splashScreenPage, NavigationStacks.Authentication.ToString());
here you can leverage James Montemagno's Settings Plugin
if (Settings.IsUserLoggedIn)
{
MainPage = mainNavigation;
}
else
{
MainPage = authenticationNavigation;
}
So far you had already done the code above. But the idea for the problem is to clear the authentication stack except the root page i.e splash Screen:
public static void PopToStackRoot(NavigationStacks navigationStack)
{
switch (navigationStack)
{
case NavigationStacks.Authentication:
{
var mainPage = FreshPageModelResolver.ResolvePageModel<MainPageModel>();
var mainNavigation = new FreshNavigationContainer(MainPage, NavigationStacks.Main.ToString());
break;
}
case NavigationStacks.Main:
{
var splashScreenPage= FreshPageModelResolver.ResolvePageModel<SplashScreenPageModel>();
var authenticationNavigation = new FreshNavigationContainer(splashScreenPage, NavigationStacks.Authentication.ToString());
break;
}
}
}
And finally here is the code inside Logout command:
private void Logout()
{
Settings.IsUserLoggedIn = false;
NavigationService.PopToStackRoot(NavigationStacks.Authentication);
CoreMethods.SwitchOutRootNavigation(NavigationStacks.Authentication.ToString());
}
I know there may be better and more efficient approaches. But that worked for me.

How to show different pages when app launches time in windows phone 7?

When app launches time need to show the registration page.once user registered it shouldn't goes to registration page need to go log in page.
How to achieve this?
You can navigate to the start page of a Windows Phone app from code.
Remove the "DefaultTask" entry from the WMAppManifest
Remove the NavigationPage attribute from the "DefaultTask" in WMAppManifest, and in the Launching event of your app use the something like the example below to navigate to the page of choice upon launch.
private void Application_Launching(object sender, LaunchingEventArgs e)
{
if (registered)
{
((App)Application.Current).RootFrame.Navigate(new Uri("/<your start page>.xaml", UriKind.Relative));
}
else
{
((App)Application.Current).RootFrame.Navigate(new Uri("/<your registration page>.xaml", UriKind.Relative));
}
}
You just have to decide how you want to determine that someone already registered.
I guess you haven't put a lot of thought to this, the setup is pretty easy! When a user registers you could set a variable in the settings defining that a user already has registered. When the application starts, evaluate this setting and if the user registered you show the register-page, otherwise the login-page. Example:
//After (succesful) registration
Properties.Settings.Default.HasRegistered = true;
Properties.Settings.Default.Save();
//Check the value
var hasRegistered = Properties.Settings.Default.HasRegistered;
if(hasRegistered)
//show Login
else
//show Registration
You can also use the IsolatedStorageSettings.ApplcationSettings to do this. The code below is just sample code, you'll have to provide validation if the settings already exist on the first startup of the app and set a default value 'false' for the setting if no registration has occured yet.
//After registration
var settings = IsolatedStorageSettings.ApplicationSettings;
if (settings.Contains("HasRegistered"))
settings["HasRegistered"] = true;
settings.Save();
//Check value
var settings = IsolatedStorageSettings.ApplicationSettings;
if (settings.Contains("HasRegistered"))
{
var registered = bool.Parse(settings["HasRegistered"]);
if(registered)
//show login
else
//show registration
}
Hope this helps!

Resources