I am having problems with httpClient.GetAsync calling Rest Service in .NET MAUI

118 Views Asked by At

I am trying to call some Azure Restful functions from .NET MAUI. No matter how I configure my await statements they never seem to alert my thread properly so that my retrieved data displays.

Specifically, I open a details page from a ScrollView using Navigation.PushAsync();

I have a ViewModel for my page with an Observable collection. When I create the collection I send it the key of the object I want back from the cloud. I format a URI to perform a GetAsync from an httpClient. I call the GetAsync, I check the status and I the read the result as a string:

try
{
    httpHandler.response = await _restHandler.myClient.GetAsync(uri).ConfigureAwait(false);

    httpHandler.response.EnsureSuccessStatusCode();

    content = await httpHandler.response.Content.ReadAsStringAsync().ConfigureAwait(false);

    Items = System.Text.Json.JsonSerializer.Deserialize<List<ProtoArcher>>(content);

    if (Items.Count == 1)
    {
        _protoArcher = Items[0];
    }

}
catch (Exception ex)
{
    Debug.WriteLine(@"\tError {0}", ex.Message);
}

This code throws no exceptions. And it eventually returns my data in a few milliseconds. Elsewhere I have bound a ContentView to the ObservableCollection in some XAML. It looks like this...

ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="DRSMain.Views.RegisterArcher"
             Title="RegisterArcher">
    <ContentView x:Name="cvMain" Grid.Column="1"  Grid.Row="2" Margin="10" >
        <StackLayout BindableLayout.ItemsSource="{Binding archers}">
            <BindableLayout.ItemTemplate>
                <DataTemplate>
                    <StackLayout Orientation="Vertical">
                        <Label Text="Archer Details" VerticalOptions="End" />
                        <BoxView Color="Black" HeightRequest="3" VerticalOptions="Start" />
                        <Label Text="First Name" VerticalOptions="End"  />
                        <Entry x:Name="firstName"  VerticalOptions="End" Text="{Binding FirstName}"/>
                        <Label Text="Middle Name" VerticalOptions="End" />
                        <Entry x:Name="middleName" VerticalOptions="End" Text="{Binding MiddleName}" />
                        <Label Text="Last Name" VerticalOptions="End" />
                        <Entry x:Name="lastName" VerticalOptions="End" Text="{Binding LastName}"/>
                        <Label Text="Date of Birth" VerticalOptions="End"/>
                        <Entry x:Name="dateOfBirth" VerticalOptions="End" Text="{Binding DateOfBirth}"/>
                        <Label Text="Email" VerticalOptions="End" />
                        <Entry x:Name="email" VerticalOptions="End" Text="{Binding Email}"/>
                        <Label Text="Member Since" VerticalOptions="End" />
                        <Label x:Name="memberSince" VerticalOptions="End" Text="Today" />
                        <Label Text="Last Login" VerticalOptions="End" />
                        <Label x:Name="lastLogin" VerticalOptions="End" Text="Never" />
                        <Label Text="{Binding ArcherID}" />
                    </StackLayout>
                </DataTemplate>
            </BindableLayout.ItemTemplate>
            <BindableLayout.EmptyView>
                <StackLayout>
                    <Label Text="Archer Details" VerticalOptions="End" />
                    <BoxView Color="Black" HeightRequest="3" VerticalOptions="Start" />
                    <Label Text="First Name" VerticalOptions="End"  />
                    <Entry VerticalOptions="End" Text="No Data"/>
                </StackLayout>
            </BindableLayout.EmptyView>
        </StackLayout>
    </ContentView>

Here is my View Model that has the observable archers object in it...

namespace DRSMain.ViewModels

{ public class RegisterArcherViewModel : INotifyPropertyChanged { Archer currentArcher; IList source;

    public ObservableCollection<Archer> archers { get; private set; }


    public RegisterArcherViewModel(string anArcherID)
    {
        currentArcher = new Archer();
        source = new ObservableCollection<Archer>();
        source.Add(currentArcher);

        if (anArcherID != null)
        {
            currentArcher.ArcherID = anArcherID;
        }
        archers = new ObservableCollection<Archer>(source);

    }

    #region INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;

    void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
    #endregion
}

}

The behavior I see is that the thread getting the data is lost from the flow (the debugger never sees it) and page renders but without my fetched data being bound. When I rig it so the Observable collection has no data, I get the EmptyView, but when data comes back, I get the non-Empty layout and blank fields. I have tried everything I can think of. If I set up a timer and check the ObservableCollection, after the screen renders, the data is there, but it never gets displayed. Anyone have a suggestion for how to get this to work... seems like something everyone should be doing these days, call a service for data and display it on a page.... ugh....

I have tried forcing the OnPropertyChanged and I have tried using a timer to wake up and check for when the data has been received. I have then tried updating the UI and calling OnPropertyChanged to no avail....

1

There are 1 best solutions below

0
mpasdanis On

While ObservableCollection automatically updates the UI upon add/remove, this may not always happen. Notifying the archers' property change explicitly after updating the collection may be helpful:

OnPropertyChanged(nameof(archers));

Update UI on Main Thread: When you update your UI or the underlying data that the UI is bound to, make sure these updates happen on the main thread using Microsoft.Maui.Dispatching.Dispatcher.Dispatch.

try
{
    httpHandler.response = await _restHandler.myClient.GetAsync(uri).ConfigureAwait(false);

    httpHandler.response.EnsureSuccessStatusCode();

    content = await httpHandler.response.Content.ReadAsStringAsync().ConfigureAwait(false);

    // Assuming Items is a List<ProtoArcher>
    Items = System.Text.Json.JsonSerializer.Deserialize<List<ProtoArcher>>(content);

    // Update UI on the main thread
    Microsoft.Maui.Dispatching.Dispatcher.Dispatch(() =>
    {
        if (Items != null && Items.Count > 0)
        {
            _protoArcher = Items[0]; // Assuming this is how you update your model

            // Clear and update your ObservableCollection
            archers.Clear(); // Assuming 'archers' is your ObservableCollection in the ViewModel
            foreach (var item in Items)
            {
                archers.Add(item);
            }
        }
    });
}
catch (Exception ex)
{
    Debug.WriteLine(@"\tError {0}", ex.Message);
}