How to navigate as per MVVM architecture in Xamarin.Forms without using any frameworks like PRISM or any Navigation Service?

南笙酒味 提交于 2019-12-11 18:45:07

问题


I have a LoginPage.xaml that inherits PageBase.xaml. LoginViewModel is the ViewModel for LoginPage and it inherits BaseViewModel. BaseViewModel has Func delegate defined - OnModalNavigationRequest which is subscribed by PageBase in it's OnAppearing method and implementation is provided in HandleModalNavigationRequest method. When Login Button is clicked on LoginPage.xaml, OnSubmit method of LoginViewModel, bound through command is invoked which, on successful authentication, calls NavigateToModal method of BaseViewModel. NavigateToModel method of BaseViewModel invokes HandleModalNavigationRequest method of PageBase on Func delegate OnModalNavigationRequest after checking if it is not null.

My problem is OnModalNavigationRequest is always coming as null, indicating that there is no subscriber to it. That means OnAppearing method of PageBase is not getting called which implies that PageBase is not getting instantiated. How to resolve this issue?

I'm posting the code of sequence of events that is taking place:

Disclaimer: Code help has been taken from here

App.xaml.cs

    protected override void OnStart()
    {
        // Handle when your app starts
        var properties = Current.Properties;

        if (properties.ContainsKey("Username"))
        {
            if (properties["Username"] != null)
            {
                var loggedInUser = (string)properties["Username"];
                MainPage = new MainPage();
            }
            else
            {
                MainPage = new LoginPage();
            }
        }
        else
        {
            MainPage = new LoginPage();
        }
    }

IView

public interface IView
{
}

public interface IView<TViewModel> : IView where TViewModel : BaseViewModel
{
    TViewModel ViewModel { get; set; }
}

BaseViewModel

    public class BaseViewModel : INotifyPropertyChanged
    {
       private string title;
       public string Title
       {
         get { return GetProperty<string>("title"); }
         set { SetProperty(value, "title"); }
       }

       protected bool SetProperty<T>(T value,
        [CallerMemberName] string propertyName = "",
        Action onChanged = null)
       {
         if (!properties.ContainsKey(propertyName))
         {
            properties.Add(propertyName, default(T));
         }

         var oldValue = GetProperty<T>(propertyName);
         if (EqualityComparer<T>.Default.Equals(oldValue, value))
            return false;

         properties[propertyName] = value;
         onChanged?.Invoke();
         OnPropertyChanged(propertyName);
         return true;
       }

       protected T GetProperty<T>([CallerMemberName] string propertyName = null)
       {
         if (!properties.ContainsKey(propertyName))
         {
            return default(T);
         }
         else
         {
            return (T)properties[propertyName];
         }
       }

       public Func<BaseViewModel, Task> OnModalNavigationRequest { get; set; }

       public async Task NavigateToModal<TViewModel>(TViewModel targetViewModel) where TViewModel : BaseViewModel
      {
        await OnModalNavigationRequest?.Invoke(targetViewModel);
      }
    }

ViewResolver

    internal static class ViewResolver
    {
        public static Page GetViewFor<TargetViewModel>(TargetViewModel targetViewModel) where 
TargetViewModel : BaseViewModel, new()
        {
            var targetViewName = targetViewModel.GetType().Name.Replace("ViewModel", "Page");
            var definedTypes = targetViewModel.GetType().GetTypeInfo().Assembly.DefinedTypes;
            var targetType = definedTypes.FirstOrDefault(t => t.Name == targetViewName);
            return Activator.CreateInstance(targetType.AsType()) as Page;
        }
    }

PageBase.xaml

    <?xml version="1.0" encoding="utf-8" ?>
    <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
         xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
         xmlns:d="http://xamarin.com/schemas/2014/forms/design"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
         mc:Ignorable="d"
         x:Class="IPMS.Views.Shared.PageBase">
    </ContentPage>

PageBase.xaml.cs

    public partial class PageBase<TViewModel> : ContentPage, IView<TViewModel> where TViewModel : BaseViewModel, new()
   {
    public TViewModel ViewModel
    {
        get
        {
            return GetValue(BindingContextProperty) as TViewModel;
        }
        set
        {
            SetValue(BindingContextProperty, value);
        }
    }

    #region <Events>

    protected override void OnAppearing()
    {
        base.OnAppearing();

        ViewModel.OnModalNavigationRequest = HandleModalNavigationRequest;
    }

    protected override void OnDisappearing()
    {
        base.OnDisappearing();

        ViewModel.OnModalNavigationRequest = null;
    }
    #endregion

    async Task HandleModalNavigationRequest(BaseViewModel targetViewModel)
    {
        var targetView = ViewResolver.GetViewFor(targetViewModel);
        targetView.BindingContext = targetViewModel;
        await Navigation.PushModalAsync(new NavigationPage(targetView));
    }
}

LoginPage.xaml

    <?xml version="1.0" encoding="utf-8" ?>
    <views:PageBase xmlns="http://xamarin.com/schemas/2014/forms"
         xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
         xmlns:views="clr-namespace:IPMS.Views.Shared;assembly=IPMS"
         x:Class="IPMS.Views.Shared.LoginPage"
         Title="{Binding Title}">

         <ContentPage.Resources>
           <ResourceDictionary>
             // Resources goes here
           </ResourceDictionary>
         </ContentPage.Resources>

         <ContentPage.Content>
           <StackLayout>
                <Button Command="{Binding SubmitCommand}" Text="Login" TextColor="White"  
                FontAttributes="Bold" FontSize="Large" HorizontalOptions="FillAndExpand" />
            </StackLayout>
         </ContentPage.Content>
    </views:PageBase>

LoginPage.xaml.cs

    public partial class LoginPage : PageBase
    {
        LoginViewModel ViewModel => BindingContext as LoginViewModel;

        public LoginPage() : base()
        {
            BindingContext = new LoginViewModel();

            InitializeComponent();
        }
    }

LoginViewModel.cs

        public class LoginViewModel : BaseViewModel
        {
            public Command SubmitCommand { protected set; get; }

            public LoginViewModel() : base()
            {
                Title = "Login";
                SubmitCommand = new Command(async () => await OnSubmit());
            }

            public async Task OnSubmit()
            {
                await NavigateToModal<ItemsViewModel>(new ItemsViewModel());
            }
        }

回答1:


Got it working by

  1. Adding x:TypeArguments in LoginPage.xaml as shown below:

    <views:PageBase xmlns="http://xamarin.com/schemas/2014/forms"
         xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
         xmlns:views="clr-namespace:IPMS.Views.Shared;assembly=IPMS"
         xmlns:ViewModel="clr-namespace:IPMS.ViewModels.Shared;assembly=IPMS"
         x:Class="IPMS.Views.Shared.LoginPage"
         x:TypeArguments="ViewModel:LoginViewModel"
         BackgroundImageSource="loginbg.9.jpg"
         Title="{Binding Title}">
    </views:PageBase>
    
  2. Changing the LoginPage.xaml.cs as shown below:

    public partial class LoginPage : PageBase<LoginViewModel>
    {
        public LoginPage() : base()
        {
            BindingContext = new LoginViewModel();
    
            InitializeComponent();
        }
    }
    


来源:https://stackoverflow.com/questions/59192571/how-to-navigate-as-per-mvvm-architecture-in-xamarin-forms-without-using-any-fram

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!