0

I'm playing around with a MAUI application, and have an ObservableCollection bound to a FlexLayout in Xaml ContentPage. The collection displays fine initially, but I've added some filtering logic, and after filtering the collection fails to render. This appears to only occur on WinUI, as it filters and renders correctly on the Android emulator, I've not yet set up an IoS simulator. The issue also appears to be specific to the FlexLayout, as when I change the flex layout to a ListView, it filters and renders correctly as well.

Any advice on how to fix this and continue to use the flex layout would be greatly appreciated.

The ContentPage

<?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"
             xmlns:Views="clr-namespace:Views"
             x:Class="Collection"
             Title="Collection">
    <ContentPage.Content>
        <ScrollView>
            <StackLayout>
                <!--Search-->
                <Views:SearchAndFilterView x:Name="SearchView"
                                           Placeholder="Search collection..." 
                                           TextChanged="SearchView_TextChanged"/>


                <!-- Collection-->
                <FlexLayout x:Name="CollectionFlexLayout"
                            BindableLayout.ItemsSource="{Binding Collection}"
                            HorizontalOptions="CenterAndExpand"
                            Wrap="Wrap">
                    <BindableLayout.ItemTemplate>
                        <DataTemplate>
                            <Views:CollectionCardView 
                                                      IconImageSource="{Binding Image}"
                                                      CardTitle="{Binding Name}"
                                                      Designer="{Binding Designers[0]}"
                                                      Publisher="{Binding Publishers[0]}"
                                                      YearPublished="{Binding YearPublished}"
                                                      PlayCount="{Binding NumPlays}"
                                                      MaxPlayers="{Binding MaxPlayers}"
                                                      MinPlayers="{Binding MinPlayers}"
                                                      PlayTime="{Binding PlayTime}"
                                                      MinAge="{Binding MinAge}"/>
                        </DataTemplate>
                    </BindableLayout.ItemTemplate>                    
                </FlexLayout>
            </StackLayout>
        </ScrollView>
    </ContentPage.Content>
</ContentPage>


The ContentPage code behind

public partial class Collection : ContentPage
{
    public ICollectionViewModel collectionViewModel;

    public Collection()
    {
        InitializeComponent();
        Init();
        InitAsync();
    }

    public void Init()
    {
        collectionViewModel = BootStrapper.Resolve<ICollectionViewModel>();
        BindingContext = collectionViewModel;
    }

    public async void InitAsync()
    {
        await collectionViewModel.GetCollection("testAcct");
    }

   

    private void SearchView_TextChanged(object sender, TextChangedEventArgs e)
    {
        collectionViewModel.FilterCollection(SearchView.SearchText, SearchView.AgeText, SearchView.PlayerCountText, SearchView.PlayTimeText);
    }

The ViewModel:

public class CollectionViewModel : ViewModelBase, ICollectionViewModel
    {
        private readonly ILogger _logger;
        private readonly ICollectionHandler _collectionHandler;
        private readonly ICollectionHelper _collectionHelper;
        private readonly IThingHandler _thingHandler;

        private ObservableCollection<CollectionPageItem> _collection = new();
        private IEnumerable<CollectionPageItem> _fullCollection;
        private bool _isBusy;

        public CollectionViewModel(ILogger logger, ICollectionHandler collectionHandler, IThingHandler thingHandler,
            ICollectionHelper collectionHelper)
        {
            _logger = logger;
            _collectionHandler = collectionHandler;
            _collectionHelper = collectionHelper;
            _thingHandler = thingHandler;
        }

        /// <summary>
        /// True if the ViewModel is querying data.
        /// </summary>
        public bool IsBusy
        {
            get => _isBusy;
            set { _isBusy = value; OnPropertyChanged(nameof(IsBusy)); }
        }

        /// <summary>
        /// The Collection to display.
        /// </summary>
        public ObservableCollection<CollectionPageItem> Collection
        {
            get { return _collection; }
            set { _collection = value; OnPropertyChanged(nameof(Collection)); }
        }

        /// <summary>
        /// Loads the <see cref="Collection"/> property for the given user.
        /// </summary>
        /// <param name="userName">The user to load a collection for.</param>
        public async Task GetCollection(string userName)
        {
            IsBusy = true;

            try
            {
                var collectionResponse = await _collectionHandler.GetCollectionByUserName(userName);
                var things = await _thingHandler.GetThingsById(collectionResponse.Item.Items.Select(x => x.Id).ToList());

                _fullCollection = _collectionHelper.CoalesceCollectionData(collectionResponse.Item.Items, things.Item.Things);
                Collection = _fullCollection.ToObservableCollection();

            }
            catch (Exception ex)
            {
                _logger.Error(ex.Message);
            }
            finally
            {
                IsBusy = false;
            }
        }

        //TODO: Make this work
        public void FilterCollection(string name, string age, string playercount, string time)
        {
            IEnumerable<CollectionPageItem> query = _fullCollection;

            if (!string.IsNullOrWhiteSpace(name))
            {
                query = query.Where(x => x.Name.ToLower().Contains(name));
            }

            if (int.TryParse(age, out int parsedAge))
            {
                query = query.Where(x => x.MinAge >= parsedAge);
            }

            if (int.TryParse(playercount, out int parsedPlayercount))
            {
                query = query.Where(x => x.MinPlayers >= parsedPlayercount);
            }

            if (int.TryParse(time, out int parsedTime))
            {
                query = query.Where(x => x.PlayTime <= parsedTime);
            }

            Collection = query.ToObservableCollection();
        }
    }
Justiciar
  • 356
  • 1
  • 3
  • 20

1 Answers1

1

FlexLayout seems to have some spacing issues in Windows. A workaround is setting HorizontalOptions="Center" for the FlexLayout and giving to it HeightRequest and WidthRequest values.

Riccardo Minato
  • 520
  • 2
  • 11
  • Unfortunately your suggestion results in the same issue as before. The FlexLayout simply doesn't render and is left blank after filtering. It's a tricky one to troubleshoot as there's no exceptions or errors being thrown either. – Justiciar Jun 09 '22 at 13:27
  • 1
    @Justiciar Could you try setting `HorizontalOptions="Center"` for your FlexLayout and giving to it HeightRequest and WidthRequest values (large enough obviously). I found out that your problem happens also to me with a simpler component and what I described solved the issue. – Riccardo Minato Jun 09 '22 at 13:41
  • Nice! that seems to be doing the trick. So it seems to be a size issue then. I'll have to play around with it, as they're still being sized strangely after filtering, but that definetly seems to be the right route. Thanks so much – Justiciar Jun 09 '22 at 13:53
  • *”ObservableCollection is meant to update UI when you add or remove elements, not when you assign it to a new value.”* This is not correct. Like any property, OnPropertyChanged in setter will update UI. Therefore, once the actual problem was found and solved, the original answer is not needed - the questions technique of replacing the collection will work. And may be better performance than adding items individually. Only the Update section at top is good. – ToolmakerSteve Jun 09 '22 at 15:42