1

I want to retrieve a list of materials from Firebase and display them in a picker. Problem, the response with the materials from Firebase comes only after navigating to the page and the content of the picker does not update. (Windows)

xaml.cs

public partial class CreateManufacturingStepPage : ContentPage
{
    CreateManufacturingStepViewModel viewModel;


    public CreateManufacturingStepPage(CreateManufacturingStepViewModel vm)
    {
    InitializeComponent();
        BindingContext = vm;
        viewModel = vm;

    }

    protected override void OnNavigatedTo(NavigatedToEventArgs args)
    {
        //my last try to make it work
        Task.Run(async () => {
           var x = await DBConnection.materialHandler.GetAll();
           x.ToList().ForEach(material => viewModel.Materials.Add(material));
           //also dont work: viewModel.Materials = await DBConnection.materialHandler.GetAll();
        });

        base.OnNavigatedTo(args);
    }
}

.xaml

                <Picker x:Name="rawMaterialPicker" Grid.Row="0" Grid.Column="1" HorizontalOptions="End"  ItemsSource="{Binding Material}" SelectedItem="{Binding ManufacturingStep.RawMaterial}">
                    <Picker.ItemDisplayBinding>
                        <Binding Path="MaterialDescription" />
                    </Picker.ItemDisplayBinding>
                </Picker>

.cs

    [ObservableProperty]
    ObservableCollection<Material> materials;
    public CreateManufacturingStepViewModel()
    {
        Materials = new ObservableCollection<Material>();
    }

I have already tried the workaround without success: https://github.com/dotnet/maui/issues/9739

Task.Run(async () => {
           var x = await DBConnection.materialHandler.GetAll();
           x.ToList().ForEach(material => viewModel.Materials.Add(material));
           //also dont work: viewModel.Materials = await DBConnection.materialHandler.GetAll();
});

I tried it in the OnNavigatedTo() and in the constructor of the view model.

I also tried this: https://stackoverflow.com/a/52804117/20589753

I was hoping that this would allow the picker to preserve his items.

The connection with Firebase can be ruled out as a problem, as I receive a response to my request with content.

Edit: for binding I tried already

Task.Run(async () => {
           var x = await DBConnection.materialHandler.GetAll();
           x.ToList().ForEach(material => viewModel.Materials.Add(material));
           rawMaterialPicker.ItemsSource = viewModel.Materials;
        });

and

        <Picker x:Name="rawMaterialPicker" Grid.Row="0" Grid.Column="1" HorizontalOptions="End"  ItemsSource="{Binding Materials}" SelectedItem="{Binding ManufacturingStep.RawMaterial}">
            <Picker.ItemDisplayBinding>
                <Binding Path="MaterialDescription" />
            </Picker.ItemDisplayBinding>
        </Picker>
mbbl33
  • 21
  • 5
  • Where are you setting or binding ItemsSource? – Jason Jun 24 '23 at 11:17
  • @Jason Sorry, I seem to have removed it while trying around. I tried it one time in the .xaml and once via code (see edit), but exactly the same with no useful result. – mbbl33 Jun 24 '23 at 13:29
  • I would expect that to work. You may need to do the UI updates on MainThread since you are running in a task – Jason Jun 24 '23 at 17:31

2 Answers2

1

Through the comments I found that I need to replace that

Task.Run(async () => {
           var x = await DBConnection.materialHandler.GetAll();
           x.ToList().ForEach(material => viewModel.Materials.Add(material));
           rawMaterialPicker.ItemsSource = viewModel.Materials;
        });

with this

        var dbMaterials = Task.Run(() =>  DBConnection.materialHandler.GetAll()).Result;
        dbMaterials.ToList().ForEach(material => viewModel.Materials.Add(material));

and it works.

The way it is explained here: https://stackoverflow.com/a/32429753/20589753

Edit: As I am new to C# and .net Maui and have very little knowledge of asynchronous methods in C#, I don't know exactly why it works. My guess is that by using Result it stays synchronous and forces a wait for the response from firebase. So the ObservableCollection is already filled at the start of the page and is not filled later with the response from firebase (which was probably the problem with the binding). In addition, I fill an already existing ObservableCollection (Materials). This is initialised in the construct of the viewmodels.

mbbl33
  • 21
  • 5
  • 1
    Hi, could you also explain why your code works now in case anyone else encounters the same issue. – Apodemus Jun 26 '23 at 14:11
1

As you are using a MVVM the approach should be slightly different.

Some side notes:

  1. You are using [ObservableProperty], this is not required as you are already using ObservableCollection, that type already handles the changes in the collection. So there's no need for that.
  2. The purpose of the ViewModel should be to handle all the business logic, including fetching data from the DB. To interact with the DB it is a good practice to have a class dedicated to that (usually called Service) and then consume it from the View Model.
  3. In the Page.cs file should be all interactions with the UI, animations, behaviors, etc.

Using MVVM to update the picker

CreateManufacturingStepPage.cs

public partial class CreateManufacturingStepPage : ContentPage
{
    public CreateManufacturingStepPage(CreateManufacturingStepViewModel vm)
    {
        InitializeComponent();
        BindingContext = vm;
    }
}

CreateManufacturingStepPage.xaml

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:viewModels="path to your assempby"
             x:DataType="viewModels:CreateManufacturingStepViewModel">

<!-- Make sure to change "PropertyName" according to your model -->
<Picker ItemsSource="{Binding Materials}"
        ItemDisplayBinding="{Binding PropertyName}"
        SelectedItem="{Binding SelectedMaterial}"/>

</ContentPage>

CreateManufacturingStepViewModel.cs

public partial class CreateManufacturingStepViewModel: ObservableObject
{

   public CreateManufacturingStepViewModel()
   {
        Materials = new ObservableCollection<Material>();
        // Autogenerated name
        UpdateMaterialsCommand.Execute(null);
   }


   public ObservableCollection<Material> Materials { get; set; }
   
   [ObservableProperty]
   private Material selectedMaterial;

   [RelayCommand]
   async Task UpdateMaterials()
   {
      var newMaterials = (await DBConnection.materialHandler
                         .GetAll())
                         .ToList();
      Materials.Clear();
      foreach(var material in newMaterials)
      {
           Materials.Add(material);
      }
      
   }
}

The Task UpdateMaterials is marked as a Relay Command so it can be bound from the Xaml code for things like RefreshView or things like that.

Note

This solution was based on MVVMCommunityToolkit as you are using it. If you want to use it without you have to implement the interface INotifiyPropertyChange and make the corresponding changes.

Some references