问题
So I am using the FreshMVVM Xamarin.Forms library so I can do ViewModel-first navigation in xamarin. My research tells me that this is a highly recommended practice, yet there seem to be not a whole lot of tutorials for this.
What I am trying to achieve is a login page that pops up as a modal once the user opens the app if they are not signed in.
I have modified the demo project slightly by adding the modal display button and associated Command
from the ViewModel to the first page, however, I am having trouble invoking this programmatically.
Here is what I am trying to so along with the relevant Command
code that I intend to tweak to work with a Login Page:
public NormalOneViewModel()
{
if (/*user is not already logged in*/) {
//display the login page as a modal to get the users credentials and log them in
//see below
}
}
...
ICommand _navToChild;
public ICommand NavigatePopup
{
get
{
if (_navToChild == null)
{
_navToChild = new Command(async () =>
{
await _navService.PushModalAsync<NormalModalViewModel>();
});
}
return _navToChild;
}
}
This Command
is invoked via <Button Text="Show Modal" Command="{Binding NavigatePopup}"/>
in the view associated with the viewModel above.
I have:
- tried to call the command programmatically using
NavigatePopup.Execute(null);
. This fails with an error explaining that the key cannot be found. - tried to call the command programmatically using
NavigatePopup.Execute();
. This yields the following error: - tried using the custom
NavigationService
that I have set up for this app by calling theawait NavigationService.Instance.PushModalAsync<NormalModalViewModel>();
line from the constructor of the detail page's ViewModel. This tells me that I need to make the method async. Additionally, don't really understand xamarin async too well since I am fairly new to Xamarin.Forms and C# Navigation.PushModalAsync(new LoginPage());
seems to work, but only in the contractor of the code-behind of the MasterDetailPage. this is also using theNavigation
Class which is apparently built-into xamarin. but this method only accepts aPage
and doesn't really conform to my ViewModel-First navigation strategy.
What I am primarily wondering is how do I programmatically invoke a modal (for a login page) when using ViewModel-first patterns?
EDIT 1: I am using Visual Studio for Mac
Apologies for the really confusing setup.
回答1:
Okay, so first off, apologies for the confusion regarding the way I presented this and why I was using PushModalAsync<NormalModalViewModel>
in I wanted to display a Login page. The reason for this is I was trying to modify the sample code in the repositories linked in the question in order to see how I would do this before transplanting it into the project I am working on.
My Solution
After tinkering with this for quite a while, I realized that the reason .Execute();
was failing when I was calling it from the constructor with the key not found
error was because I was calling it from the constructor. Even after separating the navigation command into a separate method instead of using a lambda (see below) and calling that from the constructor wouldn't work because, from my understanding, the page hadn't been displayed yet and my NavigationService
therefore hadn't registered it or something.
ICommand _loginNav;
public ICommand NavigatePopup
{
get
{
if (_loginNav == null)
{
_loginNav = new Command(async () => await NavigateModalLogin());
}
return _loginNav;
}
}
async Task NavigateModalLogin()
{
await _navService.PushModalAsync<NormalModalViewModel>();
}
while using Navigation.PushModalAsync(new LoginPage());
did work for me when calling it from the constructor, it wasn't using my custom navigation and I didn't really like that. So I continued looking for a better way.
Considering my realization from earlier that doing this check in the constructor wasn't going to work, I had the idea to use the OnAppearing()
and OnDisappearing()
methods provided by the Page
class in Xamarin.Forms.
However, since I am using ViewModel-first navigation, the processing of the login would need to be done in the ViewModel. So I wrote this in the code-behind of the page to effectively pass the execution to the ViewModel so I could use it:
protected async override Task OnAppearing()
{
await viewmodel.OnAppearing();
base.OnAppearing();
System.Diagnostics.Debug.WriteLine("*****Here*****");
}
Then in the ViewModel I had the OnAppearing()
method call the NavigateModalLogin()
function from earlier (yes I know this can be optimized to avoid the extra method call but whatever).
The good part about this is that the OnAppearing()
method is an Event Handler in Xamarin. Which means it supports async
. Which means I didn't have to figure out how to start off an async
/await
chain or however that works.
Tl;dr
Use the event handlers that Xamarin provides to call the same method that the command does (the event Handlers can be either synchronous or asynchronous).
Code:
In the code-behind for your page:
protected async override Task OnAppearing()
{
await viewmodel.OnAppearing();
base.OnAppearing();
System.Diagnostics.Debug.WriteLine("*****Here*****");
}
in the ViewModel for your page:
internal async Task OnAppearing()
{
await NavigateModalLogin();
}
ICommand _loginNav;
public ICommand NavigatePopup
{
get
{
if (_loginNav == null)
{
_loginNav = new Command(async () => await NavigateModalLogin());
}
return _loginNav;
}
}
async Task NavigateModalLogin()
{
await _navService.PushModalAsync<NormalModalViewModel>();
}
来源:https://stackoverflow.com/questions/49480702/how-to-present-a-modal-login-screen-using-viewmodel-first-navigation-in-xamarin