Update listview after object modification

Johan25Johan25 Member ✭✭
edited October 2018 in Xamarin.Forms

Hi,

I need your help for updating my listview after background tasks...
I have a ListView binded to my viewmodel ObservableCollection<Article> named Results where Article is a simple object (with ID, Price etc) and an ImagePath (ImageSource type).
In my viewmodel, when I update the Results collection with an Article's ImagePath (after a download), the UI don't shows the images...

Have you an idea to help me ?

Thanks

Tagged:

Answers

  • ColeXColeX Member, Xamarin Team Xamurai

    Please attach your code.

  • Johan25Johan25 Member ✭✭
    edited October 2018

    This is my xaml:

    <?xml version="1.0" encoding="utf-8" ?>
    <ContentPage
        x:Class="EvoPrixArt.Portable.SearchPage.SearchPage"
    ....
        xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
        xmlns:local="clr-namespace:EvoPrixArt.Portable.SearchPage"
        xmlns:telerikDataControls="clr-namespace:Telerik.XamarinForms.DataControls;assembly=Telerik.XamarinForms.DataControls"
        xmlns:telerikInput="clr-namespace:Telerik.XamarinForms.Input;assembly=Telerik.XamarinForms.Input"
        xmlns:telerikListView="clr-namespace:Telerik.XamarinForms.DataControls.ListView;assembly=Telerik.XamarinForms.DataControls"
        Title="Recherche"
        ios:Page.UseSafeArea="true"
        Icon="search_icon.png">
        <ContentPage.BindingContext>
            <local:ViewModelSearchPage />
        </ContentPage.BindingContext>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="40" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>
            <telerikInput:RadEntry
                x:Name="SearchBar"
                Grid.Row="0"
                Completed="SearchBar_Completed"
                WatermarkText="Rechercher un article" />
            <telerikDataControls:RadListView
                x:Name="ListSearchResults"
                Grid.Row="1"
                IsItemsReorderEnabled="True"
                IsLoadOnDemandEnabled="True"
                IsVisible="False"
                ItemsSource="{Binding Results, Mode=TwoWay}"
                LoadOnDemand="ListViewResults_LoadOnDemand"
                LoadOnDemandMode="Manual">
                <telerikDataControls:RadListView.ItemTemplate>
                    <DataTemplate>
                        <telerikListView:ListViewTemplateCell>
                            <telerikListView:ListViewTemplateCell.View>
                                <Grid HeightRequest="200">
                                    <Grid>
                                        <Grid.RowDefinitions>
                                            <RowDefinition Height="2*" />
                                            <RowDefinition Height="*" />
                                        </Grid.RowDefinitions>
                                        <Image
                                            Grid.Row="0"
                                            Grid.RowSpan="2"
                                            Grid.Column="0"
                                            Aspect="AspectFill"
                                            Source="{Binding ImgPath, Mode=TwoWay}" />
                                        <BoxView Grid.Row="1" BackgroundColor="#EAEAEC" />
                                        <Grid Grid.Row="1" Margin="10">
                                            <Grid.RowDefinitions>
                                                <RowDefinition Height="Auto" />
                                            </Grid.RowDefinitions>
                                            <Grid.ColumnDefinitions>
                                                <ColumnDefinition Width="2*" />
                                                <ColumnDefinition Width="1*" />
                                            </Grid.ColumnDefinitions>
                                            <Label
                                                Grid.Row="0"
                                                Grid.Column="0"
                                                FontSize="Medium"
                                                Opacity="1"
                                                Text="{Binding DesiAutoAd, Mode=TwoWay}"
                                                VerticalTextAlignment="Center" />
                                            <StackLayout
                                                Grid.Row="0"
                                                Grid.Column="1"
                                                HorizontalOptions="End"
                                                Orientation="Vertical"
                                                VerticalOptions="Center">
                                                <Label
                                                    FontAttributes="Bold"
                                                    FontSize="Small"
                                                    HorizontalTextAlignment="End"
                                                    Text="{Binding PrixNet, Mode=TwoWay, StringFormat='\{0\}€'}" />
                                                <Label
                                                    FontAttributes="Italic"
                                                    FontSize="Micro"
                                                    HorizontalTextAlignment="End"
                                                    Opacity="0.5"
                                                    Text="{Binding StockDisponible, Mode=TwoWay}"
                                                    TextColor="Green" />
                                            </StackLayout>
                                        </Grid>
                                    </Grid>
                                </Grid>
                            </telerikListView:ListViewTemplateCell.View>
                        </telerikListView:ListViewTemplateCell>
                    </DataTemplate>
                </telerikDataControls:RadListView.ItemTemplate>
                <telerikDataControls:RadListView.LayoutDefinition>
                    <telerikListView:ListViewGridLayout
                        HorizontalItemSpacing="6"
                        ItemLength="200"
                        SpanCount="2"
                        VerticalItemSpacing="6" />
                </telerikDataControls:RadListView.LayoutDefinition>
            </telerikDataControls:RadListView>
        </Grid>
    </ContentPage>
    

    My CS:

    using System;
    using System.Threading.Tasks;
    using Xamarin.Forms;
    
    namespace EvoPrixArt.Portable.SearchPage
    {
        public partial class SearchPage : ContentPage
        {
    
            public SearchPage()
            {
                InitializeComponent();
            }
    
            private async void SearchBar_Completed(object sender, System.EventArgs e)
            {
                await Task.Run(() =>
                {
                    (this.BindingContext as ViewModelSearchPage).SearchArt(SearchBar.Text);
                });
                await Task.Run(() =>
                {
                    (this.BindingContext as ViewModelSearchPage).LoadVignettes();
                });
            }
    
            private async void ListViewResults_LoadOnDemand(object sender, EventArgs e)
            {
            //SOME CODE
            }
        }
    }
    

    And my ViewModel:

    using EvoPrixArt.Portable.Models;
    using EvoPrixArt.Portable.Services;
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using Telerik.XamarinForms.Common;
    using Xamarin.Forms;
    
    namespace EvoPrixArt.Portable.SearchPage
    {
        public class ViewModelSearchPage : NotifyPropertyChangedBase
        {
            public ObservableCollection<Article> Results { get; set; } = new ObservableCollection<Article>();
    
            public ViewModelSearchPage() { }
    
            public void LoadMoreItems()
            {
                //SOME CODE
            }
    
            public void SearchArt(string research)
            {
                //SOME CODE
            }
    
            public void LoadVignettes()
            {
                foreach (Article art in this.Results)
                {
                    string img = AppelsWebServices.GetVignetteArticle(art.IdArt);
                    art.ImgPath = ImageSource.FromFile(img);
                }
            }
        }
    }
    

    So my LoadVignettes function in the ViewModel fill the Results collection correctly but my UI doesn't update with the images...
    The SearchArt function works well : my listview is updated for each item added in the Results collection.

    I think the list is only update when items are added or removed from the ObservableCollection but not on items modifications...

    Thanks

  • NicolasKrierNicolasKrier FRMember ✭✭✭
    edited October 2018

    Hello / Salut :) (AppelsWebServices me fait penser que tu es fr ^^)

    First, either you are sure about your type of VM and if it has been set and then you do a cast like this
    var vm = (ViewModelSearchPage)BindingContext;
    Otherwise you should prefer
    var vm = (this.BindingContext as ViewModelSearchPage);
    But don't forget to test if vm is null with something like vm?.SearchArt(SearchBar.Text); because it can be null

    Try this

    ViewModelSearchPage vm = (ViewModelSearchPage)this.BindingContext;  
    Task.Run(() => vm.SearchArt(SearchBar.Text)).ContinueWith(previousTask => vm.LoadVignettes(), TaskScheduler.FromCurrentSynchronizationContext());
    

    My guess is you are not performing UI things on the UI thread.
    You also can try a

    RaisePropertyChanged(nameof(Results));

  • Johan25Johan25 Member ✭✭

    Salut :)
    Je vais donc parler français puisque je suis démasqué :D

    J'ai modifié mon code pour correspondre à tes conseils, mais ce n'est pas mieux... Ma liste s'affiche bien mais sans les images...

  • NicolasKrierNicolasKrier FRMember ✭✭✭

    (héhé :D par contre je te conseil de rester en anglais si tu veux maximiser tes chances d'obtenir de l'aide ;) )

    Can you show how you filter items in SearchArt ?

    Can you show how what does string img = AppelsWebServices.GetVignetteArticle(art.IdArt); return ?

  • NicolasKrierNicolasKrier FRMember ✭✭✭
    edited October 2018
    <Image>
        <Image.Source>
            <UriImageSource Uri="{Binding ImgPath}"/>
        </Image.Source>
    </Image>
    
  • Johan25Johan25 Member ✭✭

    The SearchArt function just call a webservice and return me a List of "Article" that I add in my Results collection:

    public void SearchArt(string research)
            {
                List<Article> results = AppelsWebServices.GetArticlesFromResearch(research);
    
                Device.BeginInvokeOnMainThread(() =>
                {
                    this.Results.Clear();
                    foreach (Article art in results)
                    {
                        this.Results.Add(art);
                    }
                });
            }
    

    And this function works well.

    The AppelsWebServices.GetVignetteArticle() function download the image and save it in the phone ("/storage/emulated/0/" for Android) and return the complete path to this image (for exemple "/storage/emulated/0/63.jpeg" for the Article with the ID 63).

  • Johan25Johan25 Member ✭✭
    edited October 2018

    @NicolasKrier said:

    <Image>
        <Image.Source>
            <UriImageSource Uri="{Binding ImgPath}"/>
        </Image.Source>
    </Image>
    

    It doesn't work (white images)...

  • NicolasKrierNicolasKrier FRMember ✭✭✭

    Arg I post the last answer without refreshing the page and did not see your answer.
    Are all the images embedded in the app ? How do you store them ?

    I think the path might be wrong.

    https://forums.xamarin.com/discussion/54807/display-an-image-stored-locally

  • Johan25Johan25 Member ✭✭
    edited October 2018

    The images are not embedded in the app because they are downloaded from a webservice and saved in the root folder of Android in their corresponding format (PNG, JPEG...)

    The path is right because if I call the AppelWebServices.GetVignetteArticle() function in the SearchArt function the images are visible in the UI (but it's long and it's block the UI during the download):

    public void SearchArt(string research)
            {
                List<Article> results = AppelsWebServices.GetArticlesFromResearch(research);
    
                Device.BeginInvokeOnMainThread(async () =>
                {
                    this.Results.Clear();
                    foreach (Article art in results.Item1)
                    {
                        string img= AppelsWebServices.GetVignetteArticle(art.IdArt);
                        this.Results.Add(art);
                        art.ImgPath = ImageSource.FromFile(img);
                    }
                });
            }
    

    But it's not what I want to do because here images are downloaded at the same time the Article is added to the list and not after all Articles are added to the list...

Sign In or Register to comment.