ObservableCollection некорректно загружает данные

#c# #xaml #xamarin #xamarin.forms #syncfusion

#c# #xaml #xamarin #xamarin.forms #синхронизация

Вопрос:

Просто немного о том, что я пытаюсь сделать, у меня есть syncfusion listview , который обновляет данные из базы данных с помощью вызова REST api. Я получаю еженедельные данные. В представлении есть еще одна кнопка, которая должна обновлять список ежемесячными данными.

За неделю у меня есть 1 запись в БД, и список заполняется данными. После нажатия кнопки «просмотреть все» БД выдает данные, а список получает 13 записей, но представление обновляет только одну из них. Похоже, что список ограничен отображением только одного. Вот код:

  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>`
 

Любая помощь будет оценена.

Комментарии:

1. вы уверены , что TransactionList это содержит 13 элементов?

2. да, я отлаживал его. LoadTransactionDetails поместит 1 элемент в список. Тогда GetAllTransactions добавит 13 элементов в список. При использовании горячей перезагрузки, если я сохраняю xaml, он обновляет пользовательский интерфейс 13 элементами

Ответ №1:

  1. никогда не используйте Thread .Sleep() . Он блокирует пользовательский интерфейс (пользовательский интерфейс зависает / не отвечает), если он выполняется в потоке пользовательского интерфейса.

1.1. Пытаюсь что-то исправить, добавив поток.Sleep() и задача.Delay() — это еще один шаг к аду. Не вводите этот путь!

  1. Вам не нужно использовать ObservableCollection везде. Если вам не нужна наблюдаемость, используйте IList или IEnumerable или массив.
  2. Task.Run() создает новый поток. Обычно вам это не нужно. У вас проблемы с привязкой данных, потому что вам нужно убедиться, что привязка выполняется в потоке пользовательского интерфейса, чтобы иметь возможность обновлять компонент пользовательского интерфейса.
  3. Не загружайте данные в конструктор. Запускает событие загрузки и жизненного цикла, например, PageAppearing() (https://docs.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/app-lifecycle )

Обновленный код

 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. amp; 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);
}
 

Комментарии:

1. Спасибо, Свен, я попробую и дам вам знать. Я уверен, что способ, которым я занимался, был немного хакерским, но это был единственный способ работать.

2. Большое спасибо, Свен, я решил проблему. Это было связано с 2 вещами. Первым был Task.Run(), который создавал другой поток, а не обновлял пользовательский интерфейс. Второй была привязка данных библиотеки syncfusion. Я изменил его на обычный ListView, и он работает. Еще раз спасибо

Ответ №2:

Задача.Метод Run будет выполняться в отдельном потоке, который пытается обновить пользовательский интерфейс. Макет будет обновляться при добавлении элементов в основной поток. Потоки пользовательского интерфейса прерываются, что ограничивает макеты в приложении.

https://docs.microsoft.com/en-us/xamarin/ios/user-interface/ios-ui/ui-thread#background-thread-example
https://forums.xamarin.com/discussion/comment/96756