xamarinxamarin.forms

Using a view model and calling OnAppearing in ContentView


I'm using view models for my ContentPage's in my Xamarin Forms 5 app and typically call an Init() method in my view model from the OnAppearing() method in code behind.

I tried the same approach in my ContentView but it's never hitting the OnAppearing() method.

This is my ContentView code:

<ContentView xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:vm="clr-namespace:MyApp.ViewModels"
             x:Class="MyApp.MyContentView">
    <ContentView.BindingContext>
        <vm:MyViewModel/>
    </ContentView.BindingContext>
    <ContentView.Content>
        <StackLayout
            BackgroundColor="{StaticResource PrimaryDark }"
            HeightRequest="200">
            <Label
                Text="{Binding User.FullName}"
                TextColor="White"
                FontSize="Medium"
                FontAttributes="Bold"
                HorizontalOptions="CenterAndExpand"/>
        </StackLayout>
    </ContentView.Content>
</ContentView>

The view model for this content view looks like this:

public class MyViewModel : BaseViewModel
{
    User user;
    public MyViewModel()
    {
    }

    public User User
    {
        get => user;
        set
        {
            if (user == value)
                return;

            user = value;
            OnPropertyChanged();
         }
    }

    public async void Init()
    {
        // Get user info
        var data = await _dbService.GetUser();
        if(data != null)
        {
            User = data;
            OnPropertyChanged(nameof(User));
        }
    }
}

And in my code behind, this is what I'm doing:

public partial class MyContentView : ContentView
{
    MyViewModel _vm;
    public MyContentView()
    {
        InitializeComponent();
        _vm = new MyViewModel();
        BindingContext = _vm;
    }

    protected virtual void OnAppearing()
    {
        _vm.Init();
    }
}

This pattern is working nicely in my content pages but not working in a content view. What am I doing wrong here?


Solution

  • Here's what I've done and it seems to be working fine.

    First, I created a ContentView to display the flyout header which includes user's avatar and name. Notice that I set the view model for this content view in the XAML file -- see below:

    <?xml version="1.0" encoding="UTF-8"?>
    <ContentView xmlns="http://xamarin.com/schemas/2014/forms" 
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                 xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
                 xmlns:vm="clr-namespace:MyApp.ViewModels"
                 x:Class="MyApp.Views.FlyoutHeader">
        <ContentView.BindingContext>
            <vm:AppViewModel/>
        </ContentView.BindingContext>
        <ContentView.Content>
            <StackLayout
                BackgroundColor="{StaticResource PrimaryDark }"
                HeightRequest="200">
                <xct:AvatarView
                    Source="{Binding UserInfo.AvatarUrl}"
                    Size="100"
                    HorizontalOptions="CenterAndExpand"
                    VerticalOptions="CenterAndExpand"/>
                <Label
                    Text="{Binding UserInfo.FullName}"
                    TextColor="White"
                    FontSize="Medium"
                    FontAttributes="Bold"
                    HorizontalOptions="CenterAndExpand"
                    Margin="0,0,0,30"/>
            </StackLayout>
        </ContentView.Content>
    </ContentView>
    

    I then created a view model named AppViewModel that I intend to use in multiple places, including the FlyoutHeader.xaml that I shared above. Here's what AppViewModel looks like:

    public class AppViewModel : BaseViewModel
    {
       User user { get; set; }
       public AppViewModel()
       {
    
       }
    
       public User UserInfo
       {
           get => user;
           set
           {
               if (user == value)
                   return;
    
               user = value;
               OnPropertyChanged();
           }
       }
    
       public async void Init()
       {
           if(user == null || user.Id == Guid.Empty)
           {
               var data = await _dbService.GetUser();
               if(data != null)
               {
                   UserInfo = data;
                   OnPropertyChanged();
               }
           }
       }
    }
    

    Finally, in the code behind for FlyoutHeader.xaml.cs, I call the Init() method of the view model in the constructor:

    public partial class FlyoutHeader : ContentView
    {
         AppViewModel _vm;
         public FlyoutHeader()
         {
             InitializeComponent();
             _vm = new AppViewModel();
             _vm.Init();
    
             BindingContext = _vm;
         }
    }
    

    I'm actually a bit concerned that there maybe tight coupling with the UI and the async call being initiated in the constructor may tie up the UI thread and delay it. Please let me know if there's a better way to handle this.