Forum Xamarin.Forms
We are excited to announce that the Xamarin Forums are moving to the new Microsoft Q&A experience. Q&A is the home for technical questions and answers at across all products at Microsoft now including Xamarin!

We encourage you to head over to Microsoft Q&A for .NET for posting new questions and get involved today.

Xamarin Forms: Selected items get cleared when perform search in listview

SreeeeSreeee INMember ✭✭✭✭✭
edited August 17 in Xamarin.Forms

I have done the fetching of contacts from the phone using this blog.

Now I am trying to add the selection of contacts. Using a switch I have done the selection. But the selected contacts are clearing when performing a search operation.

xaml

<Switch
    Toggled="OnToggledEvent"
    HorizontalOptions="EndAndExpand"
    VerticalOptions="CenterAndExpand"/>

xaml.cs

public List<Contact> contactList;
public MainPage(IContactsService contactService)
{
    InitializeComponent();
    contactList = new List<Contact>();
    BindingContext = new ContactsViewModel(contactService);
}

void OnToggledEvent(object sender, EventArgs args)
{
    ViewCell cell = (sender as Xamarin.Forms.Switch).Parent.Parent as ViewCell;
    if (cell.BindingContext is Contact)
    {
        Contact contact = cell.BindingContext as Contact;
        if (contact != null)
        {
            if (contact != null && !contactList.Contains(contact))
            {
                contactList.Add(contact);
            }
            else if (contact != null && contactList.Contains(contact))
            {
                contactList.Remove(contact);
            }
        }
    }
    Debug.WriteLine("contactList:>>" + contactList.Count);
}

ContactsViewModel

public class ContactsViewModel : INotifyPropertyChanged
{
    IContactsService _contactService;

    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
    public string Title => "Contacts";


    string search;
    public string SearchText
    {

        get { return search; }

        set
        {

            if (search != value)
            {
                search = value;
                OnPropertyChanged("SearchText");

                if (string.IsNullOrEmpty(SearchText))
                {
                    FilteredContacts = new ObservableCollection<Contact>(Contacts);

                }

                else
                {

                    FilteredContacts = new ObservableCollection<Contact>(Contacts?.ToList()?.Where(s => !string.IsNullOrEmpty(s.Name) && s.Name.ToLower().Contains(SearchText.ToLower())));

                }
            }

        }

    }

    public ObservableCollection<Contact> Contacts { get; set; }

    ObservableCollection<Contact> filteredContacts;
    public ObservableCollection<Contact> FilteredContacts
    {
        get { return filteredContacts; }

        set
        {

            if (filteredContacts != value)
            {
                filteredContacts = value;
                OnPropertyChanged("FilteredContacts");
            }
        }
    }
    public ContactsViewModel(IContactsService contactService)
    {

        _contactService = contactService;
        Contacts = new ObservableCollection<Contact>();
        Xamarin.Forms.BindingBase.EnableCollectionSynchronization(Contacts, null, ObservableCollectionCallback);
        _contactService.OnContactLoaded += OnContactLoaded;
        LoadContacts();
        FilteredContacts = Contacts;
    }


    void ObservableCollectionCallback(IEnumerable collection, object context, Action accessMethod, bool writeAccess)
    {
        // `lock` ensures that only one thread access the collection at a time
        lock (collection)
        {
            accessMethod?.Invoke();
        }
    }

    private void OnContactLoaded(object sender, ContactEventArgs e)
    {
        Contacts.Add(e.Contact);
    }
    async Task LoadContacts()
    {
        try
        {
            await _contactService.RetrieveContactsAsync();
        }
        catch (TaskCanceledException)
        {
            Console.WriteLine("Task was cancelled");
        }
    }
}

I am adding the selected contact to a list when toggling the switch. If again click the switch I will remove the contact from the list. But the problem is when searching for a contact, already selected contacts get clear. I try to fix this using IsToggled property of switch, but no luck.

I have added a sample project here for the reference.

Best Answers

  • ColeXColeX Member, Xamarin Team Xamurai
    edited August 18 Accepted Answer

    The itemsource updates every time you search , you should add a property inside model to log the status of the switch and implement INotifyPropertyChanged .

    Model

    public class Contact : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    
        public string Name { get; set; }
        public string Image { get; set; }
        public string[] Emails { get; set; }
        public string[] PhoneNumbers { get; set; }
    
        private bool isToggled;
        public bool IsToggled { 
    
            get {
                return isToggled;
            } set {
                isToggled = value;
                OnPropertyChanged();
            } 
        }
    }
    

    Xaml

           <Switch  IsToggled="{Binding IsToggled}"
    
  • ColeXColeX Member, Xamarin Team Xamurai
    edited August 18 Accepted Answer

    Modify the method OnToggledEvent as below

        void OnToggledEvent(object sender, EventArgs args)
        {
    
    
            var s = sender as Xamarin.Forms.Switch;
            var model = s.BindingContext as Contact;
    
            if(model != null)
            {
                if (model.IsToggled && !contactList.Contains(model))
                {
                    contactList.Add(model);
                }
                else if (!model.IsToggled && contactList.Contains(model))
                {
                    contactList.Remove(model);
                }
            Debug.WriteLine("contactList:>>" + contactList.Count);
            }
        }
    

Answers

  • ColeXColeX Member, Xamarin Team Xamurai
    edited August 18 Accepted Answer

    The itemsource updates every time you search , you should add a property inside model to log the status of the switch and implement INotifyPropertyChanged .

    Model

    public class Contact : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    
        public string Name { get; set; }
        public string Image { get; set; }
        public string[] Emails { get; set; }
        public string[] PhoneNumbers { get; set; }
    
        private bool isToggled;
        public bool IsToggled { 
    
            get {
                return isToggled;
            } set {
                isToggled = value;
                OnPropertyChanged();
            } 
        }
    }
    

    Xaml

           <Switch  IsToggled="{Binding IsToggled}"
    
  • SreeeeSreeee INMember ✭✭✭✭✭
    edited August 18

    @ColeX

    I have tried like below:
    Added a new variable isToggled in Contact

    public class Contact
    {
        public string Name { get; set; }
        public string Image { get; set; }
        public string[] Emails { get; set; }
        public string[] PhoneNumbers { get; set; }
        public bool isToggled { get; set; }
    }
    

    XAML

    <Switch
        Toggled="OnToggledEvent"
        IsToggled="{Binding isToggled,Mode=TwoWay}"
        HorizontalOptions="EndAndExpand"
        VerticalOptions="CenterAndExpand"/>
    

    MainPage.xaml.cs

    public partial class MainPage : ContentPage
     {
        ContactsViewModel cvm;
        IContactsService service;
        public MainPage(IContactsService contactService)
        {
            InitializeComponent();
            service = contactService;
            BindingContext = new ContactsViewModel(contactService);
        }
    
        void OnToggledEvent(object sender, EventArgs args)
        {
            cvm = new ContactsViewModel(service);
            ViewCell cell = (sender as Xamarin.Forms.Switch).Parent.Parent as ViewCell;
            if (cell.BindingContext is Contact)
            {
                Contact contact = cell.BindingContext as Contact;
                if (contact != null)
                {
                    if (contact != null && !cvm.contactList.Contains(contact))
                    {
                        cvm.contactList.Add(contact);
                    }
                    else if (contact != null && cvm.contactList.Contains(contact))
                    {
                        cvm.contactList.Remove(contact);
                    }
                }
            }
            Debug.WriteLine("contactList:>>" + cvm.contactList.Count);
        }
    }
    

    ContactsViewModel

    public class ContactsViewModel : INotifyPropertyChanged
    {
        IContactsService _contactService;
        public List<Contact> contactList;
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
        public string Title => "Contacts";
    
        string search;
        public string SearchText
        {
            get { return search; }
            set
            {
                if (search != value)
                {
                    search = value;
                    OnPropertyChanged("SearchText");
                    for (int i = 0; i < Contacts.Count; i++)
                    {
                        if (contactList.Contains(Contacts[i]))
                        {
                            Contacts[i].isToggled = true;
                        }
                    }
    
                    if (string.IsNullOrEmpty(SearchText))
                    {
                        FilteredContacts = new ObservableCollection<Contact>(Contacts);
                    }
                    else
                    {
                        FilteredContacts = new ObservableCollection<Contact>(Contacts?.ToList()?.Where(s => !string.IsNullOrEmpty(s.Name) && s.Name.ToLower().Contains(SearchText.ToLower())));
                    }
                }
            }
        }
    
        public ObservableCollection<Contact> Contacts { get; set; }
    
        ObservableCollection<Contact> filteredContacts;
        public ObservableCollection<Contact> FilteredContacts
        {
            get { return filteredContacts; }
    
            set
            {
                if (filteredContacts != value)
                {
                    filteredContacts = value;
                    OnPropertyChanged("FilteredContacts");
                }
            }
        }
        public ContactsViewModel(IContactsService contactService)
        {
            contactList = new List<Contact>();
            _contactService = contactService;
            Contacts = new ObservableCollection<Contact>();
            Xamarin.Forms.BindingBase.EnableCollectionSynchronization(Contacts, null, ObservableCollectionCallback);
            _contactService.OnContactLoaded += OnContactLoaded;
            LoadContacts();
            FilteredContacts = Contacts;
        }
    
    
        void ObservableCollectionCallback(IEnumerable collection, object context, Action accessMethod, bool writeAccess)
        {
            // `lock` ensures that only one thread access the collection at a time
            lock (collection)
            {
                accessMethod?.Invoke();
            }
        }
    
        private void OnContactLoaded(object sender, ContactEventArgs e)
        {
            Contacts.Add(e.Contact);
        }
        async Task LoadContacts()
        {
            try
            {
                await _contactService.RetrieveContactsAsync();
            }
            catch (TaskCanceledException)
            {
                Console.WriteLine("Task was cancelled");
            }
        }
    }
    

    After this modification, adding and removing the selected contacts is not working. and getting System.Reflection.TargetInvocationException: 'Exception has been thrown by the target of an invocation.' when performing search operation.

  • SreeeeSreeee INMember ✭✭✭✭✭

    @ColeX said:
    The itemsource updates every time you search , you should add a property inside model to log the status of the switch and implement INotifyPropertyChanged .

    Model

    public class Contact : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    
        public string Name { get; set; }
        public string Image { get; set; }
        public string[] Emails { get; set; }
        public string[] PhoneNumbers { get; set; }
    
        private bool isToggled;
        public bool IsToggled { 
            
            get {
                return isToggled;
            } set {
                isToggled = value;
                OnPropertyChanged();
            } 
        }
    }
    

    Xaml

           <Switch  IsToggled="{Binding IsToggled}"
    

    What changes need to do on xaml.cs and ViewModel?

  • ColeXColeX Member, Xamarin Team Xamurai

    Check sample below

  • SreeeeSreeee INMember ✭✭✭✭✭

    @ColeX Already selected items are toggling when performing the search operation.

    But switch also has a Toggled event. When toggling the switch I am adding/removing the selected contact to a list. When performing a search, already selected contacts are removing from the list. I need the selected contact details for saving it to the database.

    void OnToggledEvent(object sender, EventArgs args)
        {
            ViewCell cell = (sender as Xamarin.Forms.Switch).Parent.Parent as ViewCell;
            if (cell.BindingContext is Contact)
            {
                Contact contact = cell.BindingContext as Contact;
                if (contact != null)
                {
                    if (contact != null && !contactList.Contains(contact))
                    {
                        contactList.Add(contact);
                    }
                    else if (contact != null && contactList.Contains(contact))
                    {
                        contactList.Remove(contact);
                    }
                }
            }
            Debug.WriteLine("contactList:>>" + contactList.Count);
        }
    
  • ColeXColeX Member, Xamarin Team Xamurai
    edited August 18 Accepted Answer

    Modify the method OnToggledEvent as below

        void OnToggledEvent(object sender, EventArgs args)
        {
    
    
            var s = sender as Xamarin.Forms.Switch;
            var model = s.BindingContext as Contact;
    
            if(model != null)
            {
                if (model.IsToggled && !contactList.Contains(model))
                {
                    contactList.Add(model);
                }
                else if (!model.IsToggled && contactList.Contains(model))
                {
                    contactList.Remove(model);
                }
            Debug.WriteLine("contactList:>>" + contactList.Count);
            }
        }
    
Sign In or Register to comment.