locked
LoginViewModel Prism Navigation return always Unhandled Exception: System.NullReferenceException RRS feed

  • Question

  • User317842 posted

    Hi everyone, this is my first post and I hope to find the right answer here. I have created a login form that works very good but what I am trying to do is after a positive response I want to navigate to my Main Menu at this point I always receive Unhandled Exception:System.NullReferenceException: Object reference not set to an instance of an object.

    LoginViewModel code:

    class LoginViewModel : BindableBase { private readonly ApiServices _apiServices = new ApiServices();

        private INavigationService _navigationService;
    
        public LoginViewModel(INavigationService navigationService)
        {
            _navigationService = navigationService;
        }
    
        public string Email { get; set; }
        public string Password { get; set; }
        public ICommand LoginCommand
        {
            get
            {
                return new Command(async () =>
                {
                    var accesstoken = await _apiServices.LoginAsync(Email, Password);
                    {
                        if (accesstoken != null)
                        {
                            await Prism.PrismApplicationBase.Current.MainPage.DisplayActionSheet("Thank you, you are logged in", "Ok", "");
    
                            Navigate();
    
                            void Navigate()
                            {
                                _navigationService.NavigateAsync("MainMenu");
                            }
                        }
    
                        else
                        {
                            await Prism.PrismApplicationBase.Current.MainPage.DisplayActionSheet("Please enter the correct credentials", "Ok", "");
                        }
                    }
    
                    Settings.AccessToken = accesstoken;
                });
    
            }
        }
    
        public LoginViewModel()
        {
            Email = Settings.Email;
            Password = Settings.Password;
        }
    }
    

    }

    ApiService

        public async Task<string> LoginAsync(string userName, string password)
        {
            var keyValues = new List<KeyValuePair<string, string>>
            {
                new KeyValuePair<string, string>("username", userName),
                new KeyValuePair<string, string>("password", password),
                new KeyValuePair<string, string>("grant_type", "password")
            };
    
            var request = new HttpRequestMessage(
                HttpMethod.Post, Constants.BaseApiAddress + "Token");
    
            request.Content = new FormUrlEncodedContent(keyValues);
    
            var client = new HttpClient();
            var response = await client.SendAsync(request);
    
            var content = await response.Content.ReadAsStringAsync();
    
            JObject jwtDynamic = JsonConvert.DeserializeObject<dynamic>(content);
    
            var accessTokenExpiration = jwtDynamic.Value<DateTime>(".expires");
            var accessToken = jwtDynamic.Value<string>("access_token");
    
            Settings.AccessTokenExpirationDate = accessTokenExpiration;
    
            Debug.WriteLine(accessTokenExpiration);
    
            Debug.WriteLine(content);
    
            return accessToken;
        }
    
    
    }
    

    }

    Tuesday, July 30, 2019 2:39 PM

Answers

  • User76049 posted

    There's a few things I'd do differently.

    The command should just call, and why not use DelegateCommand which has additional benefits, navigation is async so call it from a non async method

    ``` public DelegateCommand LoginCommand { get; }

    public LoginViewModel(INavigationService navigationService) { _navigationService = navigationService; LoginCommand = new DelegateCommand(async () => await Authenticate()); }

    public async Task Authenticate()
    {
                 var accesstoken = await _apiServices.LoginAsync(Email, Password);
                {
                    if (accesstoken != null)
                    {     
                          // Unless you want to retain the login page, do an absolute navigation and switch of animation for faster loading  
              await _navigationService.NavigateAsync("app:///MainMenu", null, false, false);
                     }
    

    ```

    //This really violates the MVVM pattern, you should write a dialog service and inject the interface or use the Prism one. await Prism.PrismApplicationBase.Current.MainPage.DisplayActionSheet("Please enter the correct credentials", "Ok", "");

    Also now you can see why your navigation is failing by looking at the Task navigation result to see what exception is been thrown, calling an async operation from void just swallows the exception and is not recommended.

    var res = await _navigationService.NavigateAsync("app:///MainMenu", null, false, false);

    • Marked as answer by Anonymous Thursday, June 3, 2021 12:00 AM
    Tuesday, July 30, 2019 2:51 PM

All replies

  • User76049 posted

    There's a few things I'd do differently.

    The command should just call, and why not use DelegateCommand which has additional benefits, navigation is async so call it from a non async method

    ``` public DelegateCommand LoginCommand { get; }

    public LoginViewModel(INavigationService navigationService) { _navigationService = navigationService; LoginCommand = new DelegateCommand(async () => await Authenticate()); }

    public async Task Authenticate()
    {
                 var accesstoken = await _apiServices.LoginAsync(Email, Password);
                {
                    if (accesstoken != null)
                    {     
                          // Unless you want to retain the login page, do an absolute navigation and switch of animation for faster loading  
              await _navigationService.NavigateAsync("app:///MainMenu", null, false, false);
                     }
    

    ```

    //This really violates the MVVM pattern, you should write a dialog service and inject the interface or use the Prism one. await Prism.PrismApplicationBase.Current.MainPage.DisplayActionSheet("Please enter the correct credentials", "Ok", "");

    Also now you can see why your navigation is failing by looking at the Task navigation result to see what exception is been thrown, calling an async operation from void just swallows the exception and is not recommended.

    var res = await _navigationService.NavigateAsync("app:///MainMenu", null, false, false);

    • Marked as answer by Anonymous Thursday, June 3, 2021 12:00 AM
    Tuesday, July 30, 2019 2:51 PM
  • User317842 posted

    Thank you NMackay for your fast response I will try to implement your suggestions and I will let you know if this suggestions are working.

    Tuesday, July 30, 2019 6:25 PM
  • User317842 posted

    Hi NMackay your method does not work, I am posting also my login xaml because I think that the problem of navigation is the relation between

    and Prism Navigation

    I am posting also the full XAML

    <NavigationPage.TitleView>
        <Label Text="GNH Control" FontSize="12" TextColor="#D77927" />
    </NavigationPage.TitleView>
    
    <ContentPage.BindingContext>
        <viewModels:LoginViewModel/>
    </ContentPage.BindingContext>
    
    <ContentPage.Content>
        <StackLayout HorizontalOptions="Center"
                     VerticalOptions="Center"
                     Spacing="10"
                     Padding="20">
            <Image Source="GNHC_Logo_C.png" />
    
            <Entry Text="{Binding Email}" Placeholder="Email" FontSize="Small" TextColor="#D77927" WidthRequest="175"
                   PlaceholderColor="White"/>
    
            <Entry Text="{Binding Password}" IsPassword="True" Placeholder="Password" FontSize="Small" TextColor="#D77927"
                   PlaceholderColor="White"/>
    
            <Button Command ="{Binding LoginCommand}"
                    Text="Login" BackgroundColor="#212B34" CornerRadius="10" BorderColor="#D77927" BorderWidth="2" TextColor="#4D8352"
                FontSize="Default"/>
        </StackLayout>
    </ContentPage.Content>
    

    Thursday, August 1, 2019 11:20 AM
  • User76049 posted

    I never said my suggestions would fix your problem, I simply showed you how your code should be structured so the navigation is done asynchronously and showed you a way to figure out how to what your issue was.

    What was the navigation result? Task should be faulted.

    Thursday, August 1, 2019 11:49 AM
  • User317842 posted

    Ok, I know but I try to replace the code with your suggestion but if you can see there is a relation between BindindContext and the constructor, if I will use your method I will break this relation and the button of login it doesn't work.

    Thursday, August 1, 2019 12:07 PM
  • User76049 posted

    I give up up, I have no idea what your on about.

    Thursday, August 1, 2019 12:16 PM