Maui windows application Label text not updating in UI

102 Views Asked by At

My purpose is to update ping status on UI. I am using MVVM pattern in MAUI desktop application.

In MainPage.xaml.cs

namespace MauiSampleApp
{
    public partial class MainPage : ContentPage
    {
        //int count = 0;

        public MainPage()
        {
            InitializeComponent();
        }

        private void OnCounterClicked(object sender, EventArgs e)
        {
            Shell.Current.GoToAsync("/Sample1");
            SampleM sampleM = new SampleM();
            sampleM.StartThreadfunction();
        }
    }
}

Sample1.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MauiSampleApp.View.Sample1"
             xmlns:vm="clr-namespace:MauiSampleApp.ViewModel"             
             xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
             
             Title="Sample1">
    <ContentPage.BindingContext>
        <vm:SampleVM/>
    </ContentPage.BindingContext>
    <VerticalStackLayout>
        <Label 
            Text="{Binding ServerStatus}"
            VerticalOptions="Center" 
            HorizontalOptions="Center" />
    </VerticalStackLayout>
</ContentPage>

SampleVM.cs

namespace MauiSampleApp.ViewModel
{
    public partial class SampleVM : ObservableObject
    {
        [ObservableProperty]        
        public string serverStatus = "Test";

        public void setServerStatus(string currStatus)
        {
            ServerStatus = currStatus;
        }
    }
}

SampleM.cs

namespace MauiSampleApp.Model
{
    internal class SampleM
    {
        public void StartThreadfunction()
        {
            Thread t1 = new Thread(() => CheckServerConnectivity());
            t1.Start();
        }

        public static void CheckServerConnectivity()
        {
            SampleVM sampleVM = new SampleVM();
            while (true)
            {
                try
                {
                    Ping x = new Ping();
                    PingReply reply = x.Send(IPAddress.Parse("192.168.x.xxx"));
                    if (reply.Status == IPStatus.Success)
                    {
                        Console.WriteLine("Address is accessible");
                        sampleVM.setServerStatus("connected");
                    }
                    else
                    {
                        Console.WriteLine("Address is not accessible");
                        sampleVM.setServerStatus("Disconnected");
                        //break;
                    }
                    Thread.Sleep(5000);
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Exception message : {0}", ex.Message);
                    sampleVM.setServerStatus(ex.Message);
                    break;
                }
            }
            return;
        }
    }
}

Label value "Test" display but thread status not updating. In debug mode "ServerStatus" is updated but not shown in UI.which step is wrong am i doing?

enter image description here

1

There are 1 best solutions below

2
Julian On

MVVM Pattern

You claim you're applying the MVVM pattern, but actually you're not. Here you can find more information about the MVVM pattern and data binding in MAUI.

The dependency graph in an MVVM application typically looks like this (where the arrow means "depends on"):

View -> ViewModel -> Model

In your case, the dependencies look like this:

View -> Model -> ViewModel

The Model usually holds data while the ViewModel is where the business logic lives, you have that confused.

Problem 1: BindingContext

Now, your code doesn't work, because you have two instances of the ViewModel, one that is created inside the XAML and used as the BindingContext

<ContentPage.BindingContext>
    <vm:SampleVM/>
</ContentPage.BindingContext>

and another one that is instantiated (incorrectly!) by what you call "Model":

public static void CheckServerConnectivity()
{
    SampleVM sampleVM = new SampleVM();

    //...
}

The UI doesn't know anything about this second instance, it is in no way related to the instance that is used as the BindingContext.

Note that your method also must not be static, because it needs to access instance data (see solution below).

Problem 2: Multiple Threads

You're creating a new instance of SampleM everytime a button gets clicked and each time, you start a new thread, which will lead to many concurrent threads, this will have unforeseen side effects like potential deadlocks, race conditions, etc.

I'm not going to go into detail here, because your current problem is related to updating the UI. However, you shouldn't be starting your own threads, it's better to schedule a task on the thread pool instead, if it needs to run in the background, e.g. by calling Task.Run(). This isn't ideal either, but threading and asynchronous programming are an entire class of topics on their own.

Solution

To fix the binding issue and make the app MVVM compliant, you should first swap Model and ViewModel around and then create only a single instance of your ViewModel which you then use as the BindingContext (last step):

Model

namespace MauiSampleApp.Model
{
    public partial class SampleM : ObservableObject
    {
        [ObservableProperty]        
        private string serverStatus = "Test";

        public void setServerStatus(string currStatus)
        {
            ServerStatus = currStatus;
        }
    }
}

ViewModel

namespace MauiSampleApp.ViewModel
{
    internal class SampleVM
    {
        public SampleM Model { get; } = new();

        public void StartThreadfunction()
        {
            Task.Run(CheckServerConnectivity);
        }

        private void CheckServerConnectivity()
        {
            while (true)
            {
                try
                {
                    Ping x = new Ping();
                    PingReply reply = x.Send(IPAddress.Parse("192.168.x.xxx"));
                    if (reply.Status == IPStatus.Success)
                    {
                        Console.WriteLine("Address is accessible");
                        Model.setServerStatus("connected");
                    }
                    else
                    {
                        Console.WriteLine("Address is not accessible");
                        Model.setServerStatus("Disconnected");
                        //break;
                    }
                    Thread.Sleep(5000);
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Exception message : {0}", ex.Message);
                    Model.setServerStatus(ex.Message);
                    break;
                }
            }
        }
    }
}

View

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MauiSampleApp.View.Sample1"
             xmlns:vm="clr-namespace:MauiSampleApp.ViewModel"             
             xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
             
             Title="Sample1">
    <VerticalStackLayout>
        <Label 
            Text="{Binding Model.ServerStatus}"
            VerticalOptions="Center" 
            HorizontalOptions="Center" />
    </VerticalStackLayout>
</ContentPage>
namespace MauiSampleApp
{
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
            SampleVM sampleVM = new SampleVM();
            sampleVM.StartThreadfunction();
            BindingContext = sampleVM;
        }
    }
}

Note that this will only fix the binding issue and prevent multiple threads from being started, it doesn't fix any other design or threading issues.