I have Masterpage which appear after I click on button.
Here is my code.
App.cs
public App()
{
// The root page of your application
MainPage = new NavigationPage(new Login());
}
Login.cs
public void button(object sender, EventArgs e)
{
MasterDetailPage fpm = new MasterDetailPage();
fpm.Master = new MasterPage(**test.Text**) { Title = "Main Page" }; // You have to create a Master ContentPage()
fpm.Detail = new NavigationPage(new PageOne()); // You have to create a Detail ContenPage()
Application.Current.MainPage = fpm;
}
From login page i'm passing parameter to Masterpage (i.e.) test.text, I want to pass this parameter to all pages in the Mastepage, but I dont know how to do it.
MasterPage.cs
public MasterPage(string id)
{
InitializeComponent();
BindingContext = new MasterViewModel();
}
MasterViewModel.cs
public ICommand NavigationCommand
{
get
{
return new Command((value) =>
{
// COMMENT: This is just quick demo code. Please don't put this in a production app.
var mdp = (Application.Current.MainPage as MasterDetailPage);
var navPage = mdp.Detail as NavigationPage;
// Hide the Master page
mdp.IsPresented = false;
switch(value)
{
case "1":
navPage.PushAsync(new PageOne());
break;
case "2":
navPage.PushAsync(new PageTwo());
break;
}
});
}
}
So basically what I need is after login I want to pass id to Masterpage, so all pages in Masterpage can get that id and based on id I can work on this project.
You can use the messaging center.
You send like this :
MessagingCenter.Send<MainPage> (this, "Hi");
You receive like this :
MessagingCenter.Subscribe<MainPage> (this, "Hi", (sender) => {
// do something whenever the "Hi" message is sent
});
And don't forget to unsubscribe :
MessagingCenter.Unsubscribe<MainPage> (this, "Hi");
Related
I'm using Deeplinking on Xamarin.iOS.
I can't find a pattern to handle the link if the user has disconnected from my app.
In this case I need to redirect to the login page while waiting for the login to complete before leaving SceneDelegate.ContinueUserActivity, right?
What I've tried so far is to set a Nito.AsyncEx.AsyncAutoResetEvent in my login ViewController, then wait for it with the help of AsyncHelpers. But the login UI is frozen.
[Export("scene:continueUserActivity:")]
public void ContinueUserActivity(UIScene scene, NSUserActivity userActivity)
{
....
if (notAuthenticated)
{
// The user is not authenticated
// Redirect to the Login Page
LoginViewController lvc = new LoginViewController(true);
if (Window.RootViewController == null)
{
var navController = new UINavigationController(lvc);
Window.RootViewController = navController;
}
else
{
UINavigationController rootViewController = ((UINavigationController)Window.RootViewController);
rootViewController.PopToRootViewController(true);
rootViewController.PushViewController(lvc, true);
}
Window.MakeKeyAndVisible();
AsyncHelpers.RunSync(async() => await lvc.LoginFinishedOrCancelled.WaitAsync());
...
}
}
As far as I know there is no way to make ContinueUserActivity async aware.
You could invoke the line AsyncHelpers.RunSync(async() => await lvc.LoginFinishedOrCancelled.WaitAsync()); in your login page instead of in SceneDelegate .
public bool isNeedLoad {get;set;}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
if(isNeedLoad)
{
// ...AsyncHelpers.RunSync(async() => await lvc.LoginFinishedOrCancelled.WaitAsync());
isNeedLoad = false ;
}
}
LoginViewController lvc = new LoginViewController(true){isNeedLoad = true};
if (Window.RootViewController == null)
{
var navController = new UINavigationController(lvc);
Window.RootViewController = navController;
Window.MakeKeyAndVisible();
}
else
{
UINavigationController rootViewController = ((UINavigationController)Window.RootViewController);
rootViewController.PopToRootViewController(true);
rootViewController.PushViewController(lvc, true);
}
I am trying to write a xamarin app that will display a login page before a master detail page but I am running into issues.
Right now I have my app.xaml calling an appbootstrapper as follows:
public App()
{
this.InitializeComponent();
RxApp.SuspensionHost.CreateNewAppState = () => new AppBootstrapper();
RxApp.SuspensionHost.SetupDefaultSuspendResume();
this.MainPage = RxApp.SuspensionHost
.GetAppState<AppBootstrapper>()
.CreateMainPage();
}
/// <summary>Gets the Router associated with this Screen.</summary>
public RoutingState Router { get; } = new RoutingState();
With the app bootstrapper as follows:
public class AppBootstrapper : ReactiveObject, IScreen
{
public AppBootstrapper(IMutableDependencyResolver dependencyResolver = null)
{
SetupLogging();
this.RegisterParts(dependencyResolver ?? Locator.CurrentMutable);
this.Router.Navigate.Execute(new LoginPageViewModel(this));
}
/// <summary>Gets the Router associated with this Screen.</summary>
public RoutingState Router { get; } = new RoutingState();
public Page CreateMainPage()
{
return new RoutedViewHost();
}
private static void SetupLogging()
{
var logger = new Logger { Level = LogLevel.Debug };
Locator.CurrentMutable.RegisterConstant(logger, typeof(ILogger));
}
private void RegisterParts(IMutableDependencyResolver dependencyResolver)
{
dependencyResolver.RegisterConstant(this, typeof(IScreen));
dependencyResolver.Register(() => new LoginPage(), typeof(IViewFor<LoginPageViewModel>));
dependencyResolver.RegisterConstant(new LoginService(), typeof(ILoginService));
}
}
This gets me to my login screen no problem, and I can perform my login operation. Then, once login is successful, I try to navigate to the master detail page, but this is where I run into issues.
public LoginPageViewModel(IScreen screen)
{
this.loginService = Locator.Current.GetService<ILoginService>();
this.HostScreen = screen ?? Locator.Current.GetService<IScreen>();
this.PrepareObservables();
}
........................................................
private void PrepareObservables()
{
...
this.LoginCommand = ReactiveCommand.CreateFromTask(
async execute =>
{
var loginSuccessful = await this.loginService.Login(this.Username, this.Password);
if (loginSuccessful)
{
this.HostScreen.Router.NavigateBack.Execute().Subscribe();
}
}, canExecuteLogin);
...
You can see that my login command is trying to perform a navigate and reset to go to the Main Page (which is my master detail page). This is not working and is resulting in an unhandled exception stating:
An object implementing IHandleObservableErrors has errored, thereby breaking its observable pipeline. To prevent this, ...>
Does anyone know what to do here? I need a good pattern for handling the use case of Login -> Master Detail Page in Xamarin Forms using ReactiveUI. Thanks.
this.LoginCommand = ReactiveCommand.CreateFromTask(
async execute =>
{
var loginSuccessful = await this.loginService.Login(this.Username, this.Password);
if (loginSuccessful)
{
this.HostScreen.Router.NavigateBack.Execute().Subscribe();
}
}, canExecuteLogin);
The above code is navigating back on successful login. I think you mean to use Router.NavigateAndReset.Execute(new MainPageViewModel()).Subscribe();
We are trying to use Touch Id with iOS using our Xamarin Forms application.
In our Xamarin Forms Application, in the App.Xaml.cs constructor we are using an interface to reference the iOS native touch id implementation:
bool _authenticatedWithTouchID = DependencyService.Get<ITouchID>().AuthenticateUserIDWithTouchID();
if (_authenticatedWithTouchID)
{
MainPage = new NavigationPage(new MainPage());
}
else
{
MainPage = new NavigationPage(new LoginPage());
}
This is the interface signature within Forms Application:
public interface ITouchID
{
bool AuthenticateUserIDWithTouchID();
}
This is the implementation of the interface within the iOS project:
[assembly: Dependency(typeof(TouchID))]
namespace GetIn.iOS
{
public class TouchID : ITouchID
{
public bool AuthenticateUserIDWithTouchID()
{
bool outcome = false;
var context = new LAContext();
if (context.CanEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, out NSError AuthError))
{
var replyHandler = new LAContextReplyHandler((success, error) => {
Device.BeginInvokeOnMainThread(() => {
if (success)
{
outcome = true;
}
else
{
outcome = false;
}
});
});
context.EvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, "Logging with touch ID", replyHandler);
};
return outcome;
}
}
}
We get a response from the outcome variable (which is true if user is authenticated) but that is not being passed back to the forms application.
We have also tried using async tasks with no luck.
Is there a recommended way we do this? We are trying to keep this as simple as possible.
You need to change your code to handle asynchronous behavior.
public class TouchID : ITouchID
{
public Task<bool> AuthenticateUserIDWithTouchID()
{
var taskSource = new TaskCompletionSource<bool>();
var context = new LAContext();
if (context.CanEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, out NSError AuthError))
{
var replyHandler = new LAContextReplyHandler((success, error) => {
taskSource.SetResult(success);
});
context.EvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, "Logging with touch ID", replyHandler);
};
return taskSource.Task;
}
}
Remember add the using on top
using System.Threading.Tasks;
And change your interface declaration
public interface ITouchID
{
Task<bool> AuthenticateUserIDWithTouchID();
}
And finally your Xamarin.Forms code...
var touchId = DependencyService.Get<ITouchID>();
var _authenticatedWithTouchID = await touchId.AuthenticateUserIDWithTouchID();
if (_authenticatedWithTouchID)
{
MainPage = new NavigationPage(new MainPage());
}
else
{
MainPage = new NavigationPage(new LoginPage());
}
Managed to get this working by using the async changes above (although you can do this without using the async method), and then doing the following:
By moving and adapting the following code from the app.xaml.cs to our MainPage.xaml.cs (our tabbed page) constructor.
var touchId = DependencyService.Get<ITouchID>();
var _authenticatedWithTouchID = await
touchId.AuthenticateUserIDWithTouchID();
if (_authenticatedWithTouchID)
{
//Do Nothing as on this page
}
else
{
//Go back to login page
Navigation.InsertPageBefore(new LoginPage(), this);
await Navigation.PopAsync();
}
The project I am working on contains the following structure:
When app is launched, user sees a Welcome page. At that point user has two options. They can either login or register. If logged in == true; then go to master detail page. Or in registration, if register == success then go to login page and follow the same process and end up in the master detail page.
-> Login Page ||
Welcome Page >> ================== || => MasterDetailPage
-> Register Page -> Login page ||
I am using MVVM Light to handle my navigation stack via INavigationService as my UI and business logic is separated via MVVM. Everything works pretty good except for I need to reset the navigation stack so the user will not be able to access any page before the "MasterDetailPage" showed above. Right now users can go back to login or registration or whatever page they were before, by using the hardware back button on Android or swiping from the left edge on iOS. Plus, There is a navigation back button on top navigation bar anyway.
My App.cs looks something like this
public App()
{
var nav = RegisterNavigationService();
SimpleIoc.Default.Register<INavigationService>(() => nav);
InitializeComponent();
var initialPage = new NavigationPage(new WelcomePage());
nav.Initialize(initialPage);
MainPage = initialPage;
}
private NavigationService RegisterNavigationService()
{
var nav = new NavigationService();
nav.Configure(Locator.LoginForm, typeof(LoginForm));
nav.Configure(Locator.RegisterSuccessPage, typeof(RegisterSuccessPage));
nav.Configure(Locator.RegistrationForm, typeof(RegistrationForm));
nav.Configure(Locator.WelcomePage, typeof(WelcomePage));
nav.Configure(Locator.MasterMainPage, typeof(MasterMainPage));
return nav;
}
On my view models, I handle the navigation commands like this:
public class LoginFormViewModel : BaseViewModel
{
private readonly INavigationService _navigationService;
public Command NavigateToMainPage { get; }
public LoginFormViewModel(INavigationService navigationService)
{
_navigationService = navigationService ?? throw new ArgumentNullException("navigationService");
NavigateToMainPage = new Command(() => NavigateToMainApp());
}
private void NavigateToMainApp()
{
_navigationService.NavigateTo(Locator.MasterMainPage);
}
}
Finally, my NavigationService.cs looks like this... I barely touched this part of the code... The only thing I tried is the 'ClearNavigationStack' method but that was a failure.
public class NavigationService : INavigationService, INavigationServiceExtensions
{
private Dictionary<string, Type> _pagesByKey = new Dictionary<string, Type>();
private NavigationPage _navigation;
public string CurrentPageKey
{
get
{
lock (_pagesByKey)
{
if (_navigation.CurrentPage == null)
{
return null;
}
var pageType = _navigation.CurrentPage.GetType();
return _pagesByKey.ContainsValue(pageType)
? _pagesByKey.First(p => p.Value == pageType).Key
: null;
}
}
}
public void GoBack()
{
_navigation.PopAsync();
}
public void NavigateTo(string pageKey)
{
NavigateTo(pageKey, null);
}
public void NavigateTo(string pageKey, object parameter)
{
lock (_pagesByKey)
{
if (_pagesByKey.ContainsKey(pageKey))
{
ConstructorInfo constructor;
object[] parameters;
var type = _pagesByKey[pageKey];
if (parameter == null)
{
constructor = type.GetTypeInfo()
.DeclaredConstructors
.FirstOrDefault(c => !c.GetParameters().Any());
parameters = new object[] { };
}
else
{
constructor = type.GetTypeInfo()
.DeclaredConstructors
.FirstOrDefault(
c =>
{
var p = c.GetParameters();
return p.Count() == 1
&& p[0].ParameterType == parameter.GetType();
});
parameters = new[] { parameter };
}
if (constructor == null)
{
throw new InvalidOperationException("No suitable constructor found for page " + pageKey);
}
var page = constructor.Invoke(parameters) as Page;
_navigation.PushAsync(page);
}
else
{
throw new ArgumentException(
string.Format("No such page: {0}. Did you forget to call NavigationService.Configure?", pageKey), "pageKey");
}
}
}
public void Configure(string pageKey, Type pageType)
{
lock (_pagesByKey)
{
if (_pagesByKey.ContainsKey(pageKey))
{
_pagesByKey[pageKey] = pageType;
}
else
{
_pagesByKey.Add(pageKey, pageType);
}
}
}
public void ClearNavigationStack()
{
lock (_pagesByKey)
{
foreach (var pageKey in _pagesByKey.Keys)
{
_pagesByKey.Remove(pageKey);
}
}
}
public void Initialize(NavigationPage navigation)
{
_navigation = navigation;
}
}
I've taken this bit from the following git repo: https://github.com/mallibone/MvvmLightNavigation.XamarinForms
by following this tutorial:
https://mallibone.com/post/xamarin.forms-navigation-with-mvvm-light
Note: It is a PCL.
Any suggestion is welcome as I've been on this for the last 2 days.
EDIT: Just now, I've managed to "hide" the nav stack by setting my MainPage to something like this
App.Current.MainPage = new MasterMainPage();
But it seems like a code smell and looks like a horrific hack. Plus I am not too sure if it "violates" the concepts I am following... And I guess this navigation stack will never be gone anyway as I will do other navigation stacks inside the master detail pages.
From your picture I see that you have Master/Detaied page inside Navigation page. Xamarin doesn't recommend to do that. I don't know how you are going to do it in MVVM Light but in regular Forms you have couple options to achieve what you want:
If you ever need to go back to your Login or register page you should use
await Navigation.PushModalAsync(new YourMasterDetailPage());
Then you can popmodal to get back to them BUT in this case Hardware button will still bring you to Login. You can use part of method 2 to clear stack after you navigated to you master-detail page but be careful - you cannot remove a page from stack if it is root and currently displayed page, so you will need to clear regular navigation stack only after login page is not displayed.
I wouldn't recommend that option as "Modal views are often temporary and brought on screen only long enough for the user to complete a task."
http://blog.adamkemp.com/2014/09/navigation-in-xamarinforms_2.html
If you don't need to go back you can use the follow to clear Navigation stack, it will also remove Back button
await Navigation.PushAsync(new YourMasterPage());
var pages = Navigation.NavigationStack.ToList();
foreach (var page in pages)
{
if (page.GetType() != typeof(YourMasterPage))
Navigation.RemovePage(page);
}
I have 2 classes and want to send object using Messenger while navigating from page to another and it works but only when navigate to the page and come back then try again not from first try.
ManivViewModel code:
public void GoToDetial(object parameter)
{
try
{
var arg = parameter as ItemClickEventArgs;
var item = arg.ClickedItem as Item;
Messenger.Default.Send<Item>(item, "Mess");
_navigationService.Navigate(typeof(DescriptionPage));
}
catch { }
}
DescriptionViewModel code:
public DescriptionViewModel(IRSSDataService rssService, INavigationService navigationService, IDialogService dialogService)
{
_dataService = rssService;
_navigationService = navigationService;
_dialogService = dialogService;
load();
LoadCommand = new RelayCommand(load);
GoToUrlCommand = new RelayCommand<object>(GoToUrl);
ShareSocialCommand = new RelayCommand(ShareSocial);
}
private void load()
{
Messenger.Default.Register<Item>(
this,
"Mess",
selectedItem =>
{
Item = selectedItem;
// Prepare content to share
RegisterForShare();
GetFromHTML(Item.Link);
});
}
I found it. I just need to pass in "true" to the Register call in the SimpleIoc to create the instance of the DescriptionViewModel immediately like this
SimpleIoc.Default.Register<DescriptionViewModel>(true);