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

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.

Related

Cannot navigate to page which was called once before

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();
}

Xamarin.Forms App return data to calling App

So, either I am asking incorrectly, or it isn't possible, let's see which...
If my app (Xamarin.Forms) is launched from another app, in order to get a url from my app, how do I return that data to the calling app? I wrongly assumed SetResult and Finish, I also wrongly assumed StartActivityForResult, but there has to be a way to do this. I know how to get data INTO my app from another app, but not the same in return.
POSSIBLE PARTIAL SOLUTION -- UPDATE, FAILS
So I have to setup an interface in my PCL, and call the method from the listview item selected handler, in the Android app I can then do this:
Intent result = new Intent("com.example.RESULT_ACTION", Uri.parse("content://result_url"));
setResult(Activity.RESULT_OK, result);
finish();
(source: https://developer.android.com/training/basics/intents/filters.html)
Is this looking right, and how would I implement the same thing on iOS?
END
I deleted my previous question because I couldn't explain the problem clearly, so here goes.
I have a Xamarin Forms app, I want to use a section of this app as a gallery. Currently I have images displayed in a list, and I have an Intent filter set that launches this page when you select the app as the source for an image (such as upload image on Facebook).
My issue is that I don't know how to return the data (the selected image) back to the app / webpage that made the request. In android I understand that you would use StartActivityForResult and OnActivityResult to handle this, but I am using Xamarin Forms (Android, iOS, UWP) and can't really find a solution that could be used cross-platform.
Just a link to documentation that covers this would be great, but if you have an example then even better.
Thanks
EDIT
Here is the code used to launch the app, I am interested in getting data back from the Intent.ActionPick after the user has selected an image from a ListView, which is in a ContentPage in the PCL.
[Activity(Label = "", Icon = "#drawable/icon", Theme = "#style/DefaultTheme", MainLauncher = true, LaunchMode = LaunchMode.SingleTop,
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
[IntentFilter(new[] { Intent.ActionSend }, Categories = new[] { Intent.CategoryDefault }, DataMimeType = #"*/*")]
[IntentFilter(new[] { Intent.ActionView, Intent.ActionPick, Intent.ActionGetContent }, Categories = new[] { Intent.CategoryDefault, Intent.CategoryOpenable }, DataMimeType = #"*/*")]
public class MainActivity : FormsAppCompatActivity
{
protected override void OnCreate(Bundle bundle)
{
try
{
base.OnCreate(bundle);
CurrentPlatform.Init();
Xamarin.Forms.Forms.Init(this, bundle);
App _app = new App();
LoadApplication(_app);
if (Intent.Action == Intent.ActionSend)
{
var image = Intent.ClipData.GetItemAt(0);
var imageStream = ContentResolver.OpenInputStream(image.Uri);
var memOfImage = new System.IO.MemoryStream();
imageStream.CopyTo(memOfImage);
_app.UploadManager(memOfImage.ToArray()); //This allows me to upload images to my app
}
else if (Intent.Action == Intent.ActionPick)
{
_app.SelectManager(); //here is where I need help
}
else
{
_app.AuthManager(); //this is the default route
}
}
catch (Exception e)
{
}
}
It seems you cannot use remote URI to provide to calling app. Some posts I checked suggest to store the file locally and provide it's path to calling app. To avoid memory leak with many files stored I suggest to use the same file name then you will have only one file at any moment.
One more note. I tested this solution in facebook. Skype doesn't seem to accept that and, again, the posts I checked saying that Skype doesn't handle Intent properly (not sure what that means).
Now to solution. In main activity for example in OnCreate method add the follow.
ReturnImagePage is the name of my page class where I select an image
Xamarin.Forms.MessagingCenter.Subscribe<ReturnImagePage, string>(this, "imageUri", (sender, requestedUri) => {
Intent share = new Intent();
string uri = "file://" + requestedUri;
share.SetData(Android.Net.Uri.Parse(uri));
// OR
//Android.Net.Uri uri = Android.Net.Uri.Parse(requestedUri);
//Intent share = new Intent(Intent.ActionSend);
//share.PutExtra(Intent.ExtraStream, uri);
//share.SetType("image/*");
//share.AddFlags(ActivityFlags.GrantReadUriPermission);
SetResult(Result.Ok, share);
Finish();
});
Above will listen for the message when the image is selected.
Then in XFroms code when image is selected dowload it, store it, get path and send to Activity using it's path. Below is my test path
MessagingCenter.Send<ReturnImagePage, string>(this, "imageUri", "/storage/emulated/0/Android/data/ButtonRendererDemo.Droid/files/Pictures/temp/IMG_20170207_174559_21.jpg");
You can use static public class to save and access results like:
public static class StaticClass
{
public static int Result;
}

Windows Phone MVVM Login Page design pattern?

I want to create a login page where the users enters username/password then a web service authenticates and saves an authentication token retrieved from the server.
I want the page view to be notified when the authentication is done successfully.
my question is: how to implement this in MVVM pattern ? I created a class for the model, a class for the model view and a class for the calling and parsing of the web service.
I can't set my ModelView as a DataContext for the page cause there are no controls that bind to the Model's data.
is this pattern an overkill or it can be implemented in another way ? please suggest.
Thanks
I have a login page that is implemented as described here. The login page itself does not have a viewmodel, but it does use a service that I wrote that contains a callback when the login completes. the service also contains other useful info about the user. I think MVVM would have been overkill here.
private void LoginButton_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(EmailTextBox.Text)) return;
if (string.IsNullOrEmpty(PasswordTextBox.Password)) return;
Login();
}
private void Login()
{
if (DeviceNetworkInformation.IsNetworkAvailable == false)
{
MessageBox.Show("I'm having trouble connecting to the internet." + Environment.NewLine + "Make sure you have cell service or are connected to WiFi then try again");
}
else
{
LoginButton.Focus(); // Removes the keyboard
UserProfile.Name = EmailTextBox.Text;
UserProfile.Password = PasswordTextBox.Password;
UserProfile.Current.Login(result =>
{
// callback could be on another thread
Dispatcher.BeginInvoke(() =>
{
// Did the login succeed?
if (result.Result)
{
NavigationService.Navigate(new Uri("/MainPage.xaml", UriKind.Relative));
}
else
{
string message = "Sorry, but I was not able to log in that user. Please make sure the name and password were entered correctly.";
MessageBox.Show(message, "Login failed");
}
});
});
}
}
You need to put ICommands in your ViewModel that point to methods who perform calls your web service, and the elements in your View should bind to those commands to perform actions.
And you need one more boolean property in your viewmodel: IsLoggedIn, that you set to true when the Login call to your webservice returns a success.
Then in your view, you can bind to IsLoggedIn to give feedback to your users.
Note: don't forget to raise PropertyChanged for IsLoggedIn in its setter.

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!

Pass value from user control (popup) to current page windows phone 7

From Mainpage, I use this to open the user control Login.xaml :
myPopup = new Popup() { IsOpen = true, Child = new Login() };
I want to enter information into the Login user control and pass that value back to Mainpage to process, how can I do this? I know how to use OnNavigateTo and From to pass value to other page but it won't work on this case because the Popup actually lays on top the MainPage, so it does not navigate anywhere.
You need to use events for that:
var login = new Login();
login.Completed += (a, b) =>
{
// retrieve login.Data
// close popup
};
myPopup = new Popup() { IsOpen = true, Child = login };
and login user control:
{
...
public event EventHandler Completed;
void OnCompleted
{
var h = Completed;
if (h!= null) h(this, new EventArgs());
}
}
remember to call OnCompleted() in the login whereever it suits (e.g. OK button handler)
Well, you could just use some global variable. However using globals is very bad design. Better option is using for example Event Aggregator pattern. Read about it here, here or here.

Resources