Search code examples
c#xamlxamarinxamarin.formssyncfusion

ObservableCollection not loading data correctly


Just a bit of background of what I am trying to do, I have a syncfusion listview that updates the data from the database with a REST api call. I get weekly data. There is another button on the view that should updates the list with monthly data.

For weekly, I have 1 record in the db and list gets populated with data. After clicking on the "view all" button, the db gives the data and list receives 13 records but the view update just one of it. Looks like the list has limited to show just one. Here is the code:

 private ObservableCollection<TransactionInformationDto> listItems;


    public ObservableCollection<TransactionChartData> ChartData { get; set; }
    public ObservableCollection<TransactionInformationDto> TransactionList
    {
        get { return listItems; }
        set { listItems = value; OnPropertyChanged(nameof(TransactionList)); }

    }

    public ICommand GetTransactions => new Command(async () =>
    {
         IsBusy = true;
         TransactionList.Clear();
         var data = await GetAllTransactions();
          foreach(var item in data)
          {
              TransactionList.Add(item);
          }
         IsBusy = false;
    });

    public Command<object> ItemTappedCommand
    {
        get
        {
            return this.itemTappedCommand ?? (this.itemTappedCommand = new Command<object>(ShowTransactionInformation));
        }
    }

    private void ShowTransactionInformation(object item)
    {
        var list = item as Syncfusion.ListView.XForms.ItemTappedEventArgs;
        var transaction = (TransactionInformationDto)list.ItemData;
        Navigation.PushAsync(new TransactionInfoPage(transaction));
    }
    #endregion

    #region Constructor
    public DashboardPageViewModel(INavigation navigation)
    {
        LoadTransactionDetails();
        
        Navigation = navigation;
    }

    #endregion

    #region Properties

    public double TotalBalance
    {
        get
        {
            return totalBalance;
        }
        set
        {
            this.totalBalance = value;
            this.OnPropertyChanged();
        }
    }

    public INavigation Navigation { get; }
    #endregion

    #region Methods
    private async Task<ObservableCollection<TransactionInformationDto>> GetAllTransactions()
    {      
           var retrievalInformation = await App.Database.GetUserRetrievalInformation();
            return new ObservableCollection<TransactionInformationDto>(await DependencyService.Get<IGetInformation>().GetAllUserTransactions(retrievalInformation));
    }

    private void LoadTransactionDetails()
    {
        var userTransactions = new List<TransactionInformationDto>();
        Task.Run(async () => {
            var retrievalInformation = await App.Database.GetUserRetrievalInformation();
            var userBalance = await DependencyService.Get<IGetInformation>().GetUserBalanceInformation(retrievalInformation);
            TotalBalance = userBalance.CurrentBalance;
            userTransactions = await DependencyService.Get<IGetInformation>().GetTransactionData(retrievalInformation);
        });

        Thread.Sleep(1000);
        WeekData(userTransactions);
    }

    private void WeekData(List<TransactionInformationDto> transactionInformation)
    {
        TransactionList = new ObservableCollection<TransactionInformationDto>();
        var data = new ObservableCollection<TransactionInformationDto>(transactionInformation.OrderByDescending(x =>x.TimeStamp));
        foreach(var item in data)
        {
            TransactionList.Add(item);
        }
        days = new string[] { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" };
        ChartData = new ObservableCollection<TransactionChartData>();
        UpdateChartData(days);
    }

XAML

<Grid Grid.Row="1">

                    <Label Margin="16,26,16,16"
                           Text="TRANSACTIONS"
                           TextColor="{StaticResource Gray-800}"
                           FontSize="12"
                           LineHeight="{OnPlatform Android=1.5, Default=-1}" 
                           HorizontalOptions="Start" />

                    <buttons:SfButton Margin="11,26,11,16"
                                      BorderWidth="0"
                                      TextColor="{StaticResource Gray-600}"
                                      BackgroundColor="{StaticResource Transparent}"
                                      WidthRequest="72"
                                      HeightRequest="18"
                                      Command="{Binding GetTransactions}"
                                      CornerRadius="4"
                                      HorizontalOptions="End">
                        <Label Text="VIEW ALL"
                               TextColor="{DynamicResource Link}"
                               FontSize="12"
                               HorizontalTextAlignment="Center"
                               VerticalTextAlignment="Center"
                               LineHeight="{OnPlatform Android=1.5, Default=-1}"
                                />
                    </buttons:SfButton>

                </Grid>

                <listView:SfListView Grid.Row="2" 
                                     x:Name="_transactionList"
                                     IsScrollBarVisible="False"
                                     ItemSpacing="0"
                                     ItemsSource="{Binding TransactionList}"
                                     SelectionBackgroundColor="{StaticResource TappedBackgroundColor}"
                                     TapCommand="{Binding ItemTappedCommand}"
                                     AutoFitMode="Height"
                                     BackgroundColor="White">
                    <listView:SfListView.ItemTemplate>
                        <DataTemplate>
                            <Grid RowSpacing="0" ColumnSpacing="0">

                                <Grid.RowDefinitions>
                                    <RowDefinition Height="Auto" />
                                    <RowDefinition Height="Auto" />
                                    <RowDefinition Height="Auto" />
                                </Grid.RowDefinitions>

                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="Auto" />
                                    <ColumnDefinition Width="*" />
                                </Grid.ColumnDefinitions>   
                                
                                 <!--Profile pic-->
                                <border:SfBorder Grid.RowSpan="3"
                                                 Margin="16"
                                                 WidthRequest="40"
                                                 HeightRequest="40"
                                                 CornerRadius="20"
                                                 BorderWidth="0"
                                                 VerticalOptions="Center">
                                    <Image Aspect="Fill"
                                           Source="receipt">
                                    </Image>
                                </border:SfBorder>

                                <!-- Name -->
                                <Label Grid.Column="1"
                                       Margin="0,15,0,4"
                                       HorizontalOptions="Start"
                                       Text="{Binding ReceiverName}"
                                       LineHeight="{OnPlatform Android=1.5, Default=-1}" />

                                <!-- Transaction Title -->
                                <Label Grid.Row="1"
                                       Grid.Column="1"
                                       Margin="0,0,0,16"
                                       HorizontalOptions="Start"
                                       Text="{Binding TransactionMessage}"
                                       TextColor="{StaticResource Gray-700}"
                                       FontSize="12"
                                       LineHeight="{OnPlatform Android=1.5, Default=-1}" />

                                <!-- Amount -->
                                <Label Grid.Column="1"
                                       Margin="0,16,16,4"
                                       HorizontalOptions="End"
                                       TextColor="{Binding IsReceived, Converter={x:StaticResource BooleanToColorConverter}, ConverterParameter=5}"
                                       LineHeight="{OnPlatform Android=1.5, Default=-1}">
                                    <Label.FormattedText>
                                        <FormattedString>
                                            <Span Text="{Binding IsReceived, Converter={StaticResource BooleanToStringConverter}, ConverterParameter=2}" />
                                            <Span Text=" $" />
                                            <Span Text="{Binding TransactionAmount}" />
                                        </FormattedString>
                                    </Label.FormattedText>
                                </Label>

                                <!-- Date -->
                                <Label Grid.Row="1"
                                       Grid.Column="1"
                                       Margin="0,0,16,16"
                                       HorizontalOptions="End"
                                       Text="{Binding TimeStamp, StringFormat='{}{0:dd MMM yyyy}'}"
                                       TextColor="{StaticResource Gray-700}"
                                       FontSize="12"
                                       LineHeight="{OnPlatform Android=1.5, Default=-1}" />

                                <!-- Seperator -->
                                <BoxView Grid.Row="2" Grid.ColumnSpan="2" Style="{StaticResource SeparatorStyle}" />

                            </Grid>

                        </DataTemplate>
                    </listView:SfListView.ItemTemplate>
                </listView:SfListView>
                <controls:Popup Grid.Row="2" Grid.RowSpan="1" IsBusy="{Binding IsBusy}" IsEnabled="{Binding IsBusy}" LoadingMessage="Loading the list.." />
            </Grid>`

Any help would be appreciated.


Solution

    1. don't ever use Thread.Sleep(). It blocks the UI (UI hangs / doesn't respond) if it's run on the UI Thread.

    1.1. Trying to fix something by adding Thread.Sleep() and Task.Delay() is a step more towards hell. Don't enter this path!

    1. You don't need to use ObservableCollection everywhere. If you don't need Observability, use IList or IEnumerable or an array.
    2. Task.Run() crates a new thread. You usually don't need this. + You have issues in databinding, because you need to ensure the binding is executed on the UI thread, to be able to update the UI component.
    3. Don't load data in the constructor. Trigger the loading in and lifecycle event like PageAppearing() (https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/app-lifecycle)

    Updated code

    private ObservableCollection<TransactionInformationDto> listItems;
    
    
    public ObservableCollection<TransactionChartData> ChartData { get; set; }
    public ObservableCollection<TransactionInformationDto> TransactionList
    {
        get { return listItems; }
        set { listItems = value; OnPropertyChanged(nameof(TransactionList)); }
    
    }
    
    public ICommand GetTransactions => new Command(async () =>
    {
         IsBusy = true;
         TransactionList.Clear();
         var data = await GetAllTransactions();
         TransactionList = new ObservableCollection<TransactionInformationDto>(data);
         IsBusy = false;
    });
    
    public Command<object> ItemTappedCommand
    {
        get
        {
            return this.itemTappedCommand ?? (this.itemTappedCommand = new Command<object>(ShowTransactionInformation));
        }
    }
    
    private void ShowTransactionInformation(object item)
    {
        var list = item as Syncfusion.ListView.XForms.ItemTappedEventArgs;
        var transaction = (TransactionInformationDto)list.ItemData;
        Navigation.PushAsync(new TransactionInfoPage(transaction));
    }
    #endregion
    
    #region Constructor
    public DashboardPageViewModel(INavigation navigation)
    {
        // LoadTransactionDetails(); // <<< see 4.
        
        Navigation = navigation;
    }
    
    #endregion
    
    #region Properties
    
    public double TotalBalance
    {
        get
        {
            return totalBalance;
        }
        set
        {
            this.totalBalance = value;
            this.OnPropertyChanged();
        }
    }
    
    public INavigation Navigation { get; }
    #endregion
    
    #region Methods
    private async Task<IEnumerable<TransactionInformationDto>> GetAllTransactions() // see 2.
    {      
           var retrievalInformation = await App.Database.GetUserRetrievalInformation();
           return await DependencyService.Get<IGetInformation>().GetAllUserTransactions(retrievalInformation);
    }
    
    private async Task LoadTransactionDetails()
    {
        var userTransactions = new List<TransactionInformationDto>();  
        // see 3.
        var retrievalInformation = await App.Database.GetUserRetrievalInformation();
        var userBalance = await DependencyService.Get<IGetInformation>().GetUserBalanceInformation(retrievalInformation);
        TotalBalance = userBalance.CurrentBalance;
        userTransactions = await DependencyService.Get<IGetInformation>().GetTransactionData(retrievalInformation);
        // see. 1. & 1.1.
        WeekData(userTransactions);
    }
    
    private void WeekData(List<TransactionInformationDto> transactionInformation)
    {
        TransactionList = new ObservableCollection<TransactionInformationDto>(transactionInformation.OrderByDescending(x =>x.TimeStamp));
        days = new string[] { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" };
        ChartData = new ObservableCollection<TransactionChartData>();
        UpdateChartData(days);
    }