Matthew Warr
Matthew Warr

Reputation: 168

Refresh Xamarin ListView on Add or Delete

I am having difficulty with refreshing a Xamarin ListView once I have deleted a record, or added one from a different view.

I have a view (RaceList.xaml) which lists races in an observable collection which it obtains from a ViewModel.

RaceList.xaml.cs

using System.Collections.Generic;
using TechsportiseApp.API;
using TechsportiseApp.API.Models;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using TechsportiseApp.MainUI.ViewModels;
using System;

namespace TechsportiseApp.MainUI
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class RaceList : ContentPage
    {
        public List<Race> Races { get; set; }

        public RaceList()
        {
            InitializeComponent();

            var viewModel = new RaceListViewModel();
            BindingContext = viewModel;



            ToolbarItems.Add(new ToolbarItem("New", "Add.png", async () =>
            {
                await Navigation.PushAsync(new RaceNew());
            }));
        }

        public void OnDelete(object sender, EventArgs e)
        {
            var menuitem = ((MenuItem)sender);
            var stringraceid = menuitem.CommandParameter.ToString();
            int raceid = Int32.Parse(stringraceid);

            RacesAPI.DeleteRace(raceid);
            MessagingCenter.Send(this, "RaceListChanged");
        }

        protected override void OnAppearing()
        {
            base.OnAppearing();
            RacesAPI.GetRaces();
            //LoadServerRegisteredCitizen is a method which i used to load items inside the listview        
        }

        async void Handle_ItemTapped(object sender, Xamarin.Forms.ItemTappedEventArgs e)
        {
            if (e.Item == null)
                return;
            var race = e.Item as Race;

            await Navigation.PushAsync(new RaceView(race));
            //Deselect Item
            ((ListView)sender).SelectedItem = null;
        }


    }
}

RaceList.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="TechsportiseApp.MainUI.RaceList"
             Title="Races">
    <!--SelectedItem="{Binding RaceSelected, Mode=TwoWay} "-->
    <ListView ItemsSource="{Binding Races}"
        ItemTapped="Handle_ItemTapped"
        SeparatorVisibility = "None"
        IsPullToRefreshEnabled="true" 
        RefreshCommand="{Binding RefreshCommand}" 
        IsRefreshing="{Binding IsBusy}">

        <ListView.ItemTemplate>
            <DataTemplate>
                <ViewCell>
                    <ViewCell.ContextActions>
                       <MenuItem Clicked="OnDelete" CommandParameter="{Binding Id}" Text="Delete" IsDestructive="True" />
                    </ViewCell.ContextActions>
                    <StackLayout>
                        <Label Text="{Binding Name}"  />
                        <Label Text="{Binding RaceDate}" />
                        <BoxView Color="#B2B2B2" HeightRequest="1" />
                    </StackLayout>
                </ViewCell>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</ContentPage>

RaceListViewModel.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading.Tasks;
using Newtonsoft.Json;
using RestSharp;
using TechsportiseApp.API.Models;
using Xamarin.Forms;
using TechsportiseApp.API;
using System.Windows.Input;
using System.Collections.ObjectModel;

namespace TechsportiseApp.MainUI.ViewModels
{
    public class RaceListViewModel : INotifyPropertyChanged
    {
        public RaceListViewModel()
        {
            _refreshCommand = new Command(RefreshList);

            MessagingCenter.Subscribe<RaceListViewModel>(this, "RaceListChanged", sender => {
                Races = RacesAPI.GetRaces();
                OnPropertyChanged("Id");
            });
        }

        public void RefreshList()
        {
            //Refreshes list on Pull to Refresh
           Races = RacesAPI.GetRaces();
        }

        int _id;
        public int Id
        {
            get
            {
                return _id;
            }
            set
            {
                if (_id != value)
                {
                    _id = value;
                    // trigger some action to take such as updating other labels or fields
                    OnPropertyChanged("Id");
                }
            }
        }

        int _name;
        public int Name
        {
            get
            {
                return _name;
            }
            set
            {
                if (_name != value)
                {
                    _name = value;

                    // trigger some action to take such as updating other labels or fields
                    OnPropertyChanged("Name");
                }
            }
        }

        private ObservableCollection<Race> _races;
        public ObservableCollection<Race> Races
        {
            get
            {
                var racelist = RacesAPI.GetRaces();
                IsBusy = false;
                return racelist;
            }
            set
            {
                if (_races != value)
                {
                    _races = value;
                    // trigger some action to take such as updating other labels or fields
                    OnPropertyChanged("Races");
                }
            }
        }

        private Race _raceSelected;
        public Race RaceSelected
        {
            get
            {
                return RaceSelected;
            }
            set
            {
                if (RaceSelected != value)
                {
                    RaceSelected = value;
                    OnPropertyChanged("RaceSelected");
                }
            }
        }



        private bool _isBusy;
        public bool IsBusy
        {
            get { return _isBusy; }
            set
            {
                if (_isBusy == value)
                    return;

                _isBusy = value;
                OnPropertyChanged("IsBusy");
            }
        }



        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(string propertyName)
        {
            var changed = PropertyChanged;
            if (changed != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        Command _refreshCommand;
        public Command RefreshCommand
        {
            get
            {
                return _refreshCommand;
            }
        }
    }
}

On pressing the "Create" button in the toolbar, this takes me to a new View which shows a blank form, allowing me to fill in the data for a new race. When this is Saved, it pushes the JSON to my API and if successful it then redirects them to the edit page.

RaceNew.xaml

<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
             xmlns:sys="clr-namespace:System;assembly=mscorlib"
             x:Class="TechsportiseApp.MainUI.RaceNew"
             Title="New Race">
    <ContentPage.Content>
    <ScrollView  Orientation = "Vertical" VerticalOptions="StartAndExpand">
    <StackLayout Padding="0,20,0,0">
        <Label x:Name="labelName" Text="Name" />
            <Entry x:Name="entryName"  />
        <Label x:Name="labelDescription" Text="Description" />
            <Editor x:Name="editorDescription"  />
       <Label x:Name="labelContactName" Text="Contact Name" />
            <Entry x:Name="entryContactName"  />
        <Label x:Name="labelContactNumber" Text="Contact Number" />
            <Entry x:Name="entryContactNumber" Keyboard="Telephone"  />
        <Label x:Name="labelContactEmail" Text="ContactEmail" />
            <Entry x:Name="entryContactEmail" Keyboard="Email"   />
        <Label x:Name="labelRaceDate" Text="Race Date" />
                <DatePicker x:Name="datepickerRaceDate" Date="{Binding Source={x:Static sys:DateTime.Now}" Format="ddd d MMMM yyyy" />
        <Label x:Name="labelRaceStartTime" Text="Race Time" />
                <TimePicker x:Name="timepickerRaceStartTime"  />
        <Label x:Name="labelMaxEntries" Text="Max Entries" />
                <Entry x:Name="entryMaxEntries"  Keyboard="Numeric"  />
        <Label x:Name="labelCurrentEntries" Text="Current Entries" />
                <Entry x:Name="entryCurrentEntries"  Keyboard="Numeric" />
        <Label x:Name="labelIsOpenForEntries" Text="Open For Entries" />
            <Switch x:Name="switchIsOpenForEntries"  />
        <Label x:Name="labelIsPublished" Text="Published" />
            <Switch x:Name="switchIsPublished"  />        
    </StackLayout>
    </ScrollView>
    </ContentPage.Content>
</ContentPage>

RaceNew.xaml.cs

using System;
using TechsportiseApp.API;
using TechsportiseApp.API.Models;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace TechsportiseApp.MainUI
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class RaceNew : ContentPage
    {
        public RaceNew()
        {
            InitializeComponent();

            ToolbarItems.Add(new ToolbarItem("Create", "", async () =>

            {
                var saverace = new Race();
                saverace.ContactEmail = entryContactEmail.Text;
                saverace.ContactName = entryContactName.Text;
                saverace.ContactNumber = entryContactNumber.Text;
                saverace.CurrentEntries = 0;
                saverace.Description = editorDescription.Text;
                saverace.IsOpenForEntries = switchIsOpenForEntries.IsToggled;
                saverace.IsPublished = switchIsPublished.IsToggled;
                saverace.IsComplete = false;
                saverace.MaxEntries = System.Convert.ToInt32(entryMaxEntries.Text);
                saverace.Name = entryName.Text;
                saverace.RaceDate = datepickerRaceDate.Date;

                var timestring = string.Format("{0:00}:{1:00}:{2:00}", timepickerRaceStartTime.Time.Hours, timepickerRaceStartTime.Time.Minutes, timepickerRaceStartTime.Time.Seconds);
                saverace.RaceStartTime = timestring;

                var response = RacesAPI.CreateRace(saverace);
                if (response.Code == "Created")
                {
                    saverace.Id = response.ReturnedId;
                    Navigation.InsertPageBefore(new RaceView(saverace), this);
                    await Navigation.PopAsync();

                }
                //Error response
                else
                {
                    await DisplayAlert("Error: " + response.Code, "There has been an error creating your race. " + response.Content, "OK");
                }
            }));
        }


        async void OnBackButtonClicked(object sender, EventArgs e)
        {
            await Navigation.PopAsync();
        }
    }
}

From this page I simply press the Back button and it returns me back to my RaceList view.

The problem is, the list view doesn't refresh - it doesn't register that the new race is added.

Furthermore, in my RaceList.xaml.cs you can see I also have a Delete button in use. When I delete the item from the list it doesn't refresh either!

Any ideas?

Upvotes: 0

Views: 6220

Answers (2)

Ibrahim Malluf
Ibrahim Malluf

Reputation: 657

override the Xamarin.Forms.Page.OnAppearing Method and call the ViewModel's RefreshList() method.

RacelistViewModel Viewmodel = (RaceListViewModel)This.DataContext; ViewModel.RefreshList();

Upvotes: 1

Alessandro Caliaro
Alessandro Caliaro

Reputation: 5768

I don't know, but

public List<Race> Races { get; set; }

is a List, not an ObservableCollection.

Upvotes: 0

Related Questions