Forum Xamarin.Forms
We are excited to announce that the Xamarin Forums are moving to the new Microsoft Q&A experience. Q&A is the home for technical questions and answers at across all products at Microsoft now including Xamarin!

We encourage you to head over to Microsoft Q&A for .NET for posting new questions and get involved today.

Calling a method in the viewmodel from OnAppearing in codebehind

Kinetic55Kinetic55 Member ✭✭

Hi,

I'm trying to populate a username and password in my xaml page when the page loads.
The strings in class LoginViewPageModel are LoginEmail and LoginPassword and have bindings to my text boxes, they work fine if I call from a command binding, but I want to change them when the page loads using a threaded task, and it doesn't work/I don't know how to do it, please help :smile:

I have my mainpage.xaml, and then I call a modal page in front of it - LoginPage.xaml - All good.

I have overridden onappearing in my LoginPage.xaml, and then I have a task run as async:

    protected override void OnAppearing()
    {
        base.OnAppearing();
        Task.Run(async () =>
        {
            // LoginPageViewModel.SetLoginDetails(); // this doesn't work as SetLoginDetails is not static

    // This doesn't work either as I'm creating an instance of the LoginPageViewModel, and then I am changing
    // _loginPageViewModel.LoginEmail, instead of LoginPageViewModel.LoginEmail
            LoginPageViewModel _loginPageViewModel = new LoginPageViewModel();
            _loginPageViewModel.SetLoginDetails();
        });
    }

In LoginPageViewModel.cs, I've got my method:
public async void SetLoginDetails()
{
try
{
LoginEmail = await SecureStorage.GetAsync("username");
Console.WriteLine("*** *** *** LoginEmail is: " + LoginEmail);

            LoginPassword = await SecureStorage.GetAsync("password");
            Console.WriteLine("*** *** *** LoginPassword is: " + LoginPassword);

        }
        catch (Exception ex)
        {
            // Possible that device doesn't support secure storage on device.
            Console.WriteLine("*** *** *** SetLoginDetails() exception: " + ex);
        }

    }

Best Answers

  • Kinetic55Kinetic55 Member ✭✭
    Accepted Answer

    OK it seems that either I've missed something fundamental, or this is poorly documented. (I've seen it both ways). You're setting the bindingcontext of the stacklayout to an instance of the ViewModel.

    I've done this by setting a static instance in my LoginPageViewModel : INotifyPropertyChanged public class:
    public static LoginPageViewModel myViewModel;

    Then in one of the constructors (page or otherwise, I don't imagine it makes much of a difference but I put it in LoginPage() constructor) I'll create it:
    myViewModel = new LoginPageViewModel();

    Then I'll be free to set my name in the xaml as you suggested, in the <stacklayout tag I'll add x:Name="LoginPageLayout">

    and I'll set the BindingContext in code in the constructor, under the creation:
    LoginPageLayout.BindingContext = myViewModel;

    I was then able to run async in my task:
    myViewModel.SetLoginDetails();

    But I also had to change all my other bindings to bind to the instance of the viewmodel (myViewModel).
    I made it easier by adding a:
    using static NameSpace.LoginPageViewModel;
    at the top of my code behind on LoginPage.xaml.cs

    In the DataBindingDemos on the docs.microsoft.com site, they do not do this, however I have seen it in Mastering xamarin.forms book... I assumed given the official docs that it was optional, but it looks like it is poor practice.

    Thanks YelinZh, this makes more sense now :smiley:

Answers

  • Kinetic55Kinetic55 Member ✭✭
    edited August 17

    I found a crappy work-around.

    in my xaml, I can create an (anything clickable, but can also be hidden).

    Then in my viewmodel constructor I've got the command:
    public LoginPageViewModel()
    {
    OnButtonSmallClicked = new Command(
    execute: () =>
    {
    SetLoginDetails();
    });
    }

    In my code behind, I can now run a task async in my overriden OnAppearing:
    protected override void OnAppearing()
    {
    base.OnAppearing();
    Task.Run(async () =>
    {
    btnSmall.Command.Execute(null);
    }
    }

    This will execute the code in the binding... But it's a terrible work around I know!

  • Kinetic55Kinetic55 Member ✭✭

    How can I just call LoginPageViewModel.SetLoginDetails(); ?

  • JarvanJarvan Member, Xamarin Team Xamurai
    edited August 17

    How did you set BindingContext for the page with the ViewModel class? In the page.xaml? Try to set the BindingContext in the code behind, you will be able to get the instance object to call the method.

    Check the code:

    public partial class Page5 : ContentPage
    {
        LoginPageViewModel model;
        public Page5()
        {
            InitializeComponent();
    
            model = new LoginPageViewModel();
            BindingContext = model;
        }
    
        protected override void OnAppearing()
        {
            base.OnAppearing();
            model.SetLoginDetails();
        }
    }
    
  • Kinetic55Kinetic55 Member ✭✭
    edited August 17

    Within :

    * <StackLayout.BindingContext>
    * local:LoginPageViewModel/>
    * </StackLayout.BindingContext>

    As above I've already tried that. (see the commented out bit) You can't create an instance of your viewmodel within a page constructor and then access it from OnAppearing(), it won't exist in that context.

    Notwithstanding, the doco reads that changing the binding context is quite a bit of a performance hit - and if i change it to an instance of my ModelView class, my other bindings are going to fail.

  • Kinetic55Kinetic55 Member ✭✭
    Accepted Answer

    OK it seems that either I've missed something fundamental, or this is poorly documented. (I've seen it both ways). You're setting the bindingcontext of the stacklayout to an instance of the ViewModel.

    I've done this by setting a static instance in my LoginPageViewModel : INotifyPropertyChanged public class:
    public static LoginPageViewModel myViewModel;

    Then in one of the constructors (page or otherwise, I don't imagine it makes much of a difference but I put it in LoginPage() constructor) I'll create it:
    myViewModel = new LoginPageViewModel();

    Then I'll be free to set my name in the xaml as you suggested, in the <stacklayout tag I'll add x:Name="LoginPageLayout">

    and I'll set the BindingContext in code in the constructor, under the creation:
    LoginPageLayout.BindingContext = myViewModel;

    I was then able to run async in my task:
    myViewModel.SetLoginDetails();

    But I also had to change all my other bindings to bind to the instance of the viewmodel (myViewModel).
    I made it easier by adding a:
    using static NameSpace.LoginPageViewModel;
    at the top of my code behind on LoginPage.xaml.cs

    In the DataBindingDemos on the docs.microsoft.com site, they do not do this, however I have seen it in Mastering xamarin.forms book... I assumed given the official docs that it was optional, but it looks like it is poor practice.

    Thanks YelinZh, this makes more sense now :smiley:

Sign In or Register to comment.