winui-3uno-platformcommunity-toolkit-mvvm

Uno Platform UI not updating Loader when Observable property is updated ViewModel


I am developing a client using Uno Platform and WINUI3. I have small requirement to show the loader when some external API call is being made asynchronously for getting data. In fact i need this functionality across all pages.

i am using x:Bind to bind 'IsBusy' Observable Property of viewmodel (using CommunityToolkit.MVVM 8.0). when 'IsBusy' property set to 'true' in async InvokeCommand raised by button click, i am expecting it to start showing the loader in UI. and once my external API call is complete i set it back to 'false' so that loader will stop showing.

Unfortunately, this does not work as expected. what am i missing here? complete code is below.

MainPage.Xaml:

<Page
    x:Class="TestMVVM.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:TestMVVM"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"    
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Page.Resources>
        <local:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
    </Page.Resources>
    
    <Grid>
      <!--<TextBlock Text="Hello, world!" Margin="20" FontSize="30" />-->
        <ProgressRing  Name="myLoader" Margin="0,0,5,0"  Height="50" Width="50" IsActive="{x:Bind ViewModel.IsBusy}"/>
        <TextBlock  Name="txtLoader" FontWeight="SemiBold" FontSize="25" Text="Loading..." Visibility="{x:Bind ViewModel.IsBusy, Converter={StaticResource BoolToVisibilityConverter}}"/>
        
                
        <Button Content="CLick me" Command="{x:Bind ViewModel.ItemInvokedCommand}"></Button>
    </Grid>
</Page>

MainPage.Xaml.cs:

public sealed partial class MainPage : Page
    {
        public MainPageViewModel ViewModel { get; } = new MainPageViewModel();       
        public MainPage()
        {
            this.InitializeComponent();
            //this.DataContext = ViewModel; // i tried to set like this as well but no luck
        }
    }

ViewModel:

public partial class MainPageViewModel : ObservableObject
    {
        private IAsyncRelayCommand _itemInvokedCommand;
        public IAsyncRelayCommand ItemInvokedCommand => _itemInvokedCommand ?? (_itemInvokedCommand = new AsyncRelayCommand<TypedEventHandler<object, object>>(OnItemInvoked));

        [ObservableProperty]
        private bool isBusy;

        private async Task OnItemInvoked(TypedEventHandler<object, object> args)
        {
            
            IsBusy = true;

            //in real time i make some external API call here            
            await Task.Delay(TimeSpan.FromSeconds(10));

            IsBusy = false;
        }
    }

Solution

  • You're missing Mode=OneWay.

    The default mode of x:Bind is OneTime (as opposed to the default mode of Binding which is OneWay). Reference

    <ProgressRing (...) IsActive="{x:Bind ViewModel.IsBusy, Mode=OneWay}"/>
    <TextBlock (...) Visibility="{x:Bind ViewModel.IsBusy, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}"/>