Making a dynamic set of 3 pickers on Xamarin.Forms

Viewed 779

we are developing a cross platform app on Xamarin.Forms.
On one of the pages we need to display a set of 3 pickers with the same list of items. The idea is that when you select an item on one of the pickers it gets removed from the item-source of the other two.
To do this we developed the following code:
We started with a list of Items called BaseList which we get from a web service. We also create 3 separate lists (ListA, ListB and ListC) and 3 Items to store the selected Items of each picker (SelectedA, SelectedB and SelectedC).

private List<Item> BaseList;
private List<Item> _ListA;
private Item _SelectedA;
private List<Item> _ListB;
private Item _SelectedB;
private List<Item> _ListC;
private Item _SelectedC;
…
//Api Calls
private void LoadData()
{
         …
    BaseList = new List<Item> (ListFromWebServices);

    _ListA = new List<Item>(BaseList);
    OnPropertyChanged(nameof(ListA));
    _ListB = new List<Item>(BaseList);
    OnPropertyChanged(nameof(ListB));
    _ListC = new List<Item>(BaseList);
    OnPropertyChanged(nameof(ListC));
}
…
//Public Fields
public List<Item> ListA
{
    get
    {
        return _ListA;
    }
}

public Item SelectedA
{
    get
    {
        return _SelectedA;
    }
    set
    {
        SetProperty(ref _SelectedA, value, nameof(SelectedA));
    }
}

public List<Item> ListB
{
    get
    {
        return _ListB;
    }
}

public Item SelectedB
{
    get
    {
        return _SelectedB;
    }
    set
    {
        SetProperty(ref _SelectedB, value, nameof(SelectedB));
    }
}

public List<Item> ListC
{
    get
    {
        return _ListC;
    }
}

public Item SelectedC
{
    get
    {
        return _SelectedC;
    }
    set
    {
        SetProperty(ref _SelectedC, value, nameof(SelectedC));
    }
}

This code is on our ViewModel, so we use SetProperty to set the referenced property to the value and invoke PropertyChangedEventArgs from INotifyPropertyChanged

public event PropertyChangedEventHandler PropertyChanged;

protected bool SetProperty<T>(ref T storage, T value,
                              [CallerMemberName] string propertyName = null)
{
    if (Equals(storage, value))
        return false;

    storage = value;
    OnPropertyChanged(propertyName);
    return true;
}

protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

In order to update the ItemSource's, whenever a selected item is changed we call OnSelectedItemChanged from the setters of SelectedA, SelectedB and SelectedC. This method receives an index which indicates which Picker triggered it:

private void OnSelectedItemChanged(int index)
{
Item CurrentA = SelectedA;
Item CurrentB = SelectedB;
Item CurrentC = SelectedC;
    int i;
    switch (index)
    {
        case 0:
            _ListB = new List<Item> (BaseList);
            _ListB.Remove(CurrentA);
            _ListB.Remove(CurrentC);
            OnPropertyChanged(nameof(ListB));
            _ListC = new List<Item>(BaseList);
            _ListC.Remove(CurrentA);
            _ListC.Remove(CurrentB);
            OnPropertyChanged(nameof(ListC));

            i = ListB.IndexOf(CurrentB);
            if (i > -1)
            {
                _SelectedB = ListB[i];
            }
            OnPropertyChanged(nameof(SelectedB));
            i = ListC.IndexOf(CurrentC);
            if (i > -1)
            {
                _SelectedC = ListC[i];
            }
            OnPropertyChanged(nameof(SelectedC));

            break; 
        case 1:
            _ListA = new List<Item>(BaseList);
            _ListA.Remove(CurrentB);
            _ListA.Remove(CurrentC);
            OnPropertyChanged(nameof(ListA));
            _ListC = new List<Item>(BaseList);
            _ListC.Remove(CurrentA);
            _ListC.Remove(CurrentB); 
            OnPropertyChanged(nameof(ListC));

            i = ListA.IndexOf(CurrentA);
            if (i > -1)
            {
                _SelectedA = ListA[i];
            }
            OnPropertyChanged(nameof(SelectedA));
            i = ListC.IndexOf(CurrentC);
            if (i > -1)
            {
                _SelectedC = ListC[i];
            }
            OnPropertyChanged(nameof(SelectedC));

            break;
         case 2:
            _ListA = new List<Item>(BaseList);
            _ListA.Remove(CurrentB);
            _ListA.Remove(CurrentC);
            OnPropertyChanged(nameof(ListA));
            _ListB = new List<Item>(BaseList);
            _ListB.Remove(CurrentA);
            _ListB.Remove(CurrentC); 
            OnPropertyChanged(nameof(ListB));

           i = ListA.IndexOf(CurrentA);
            if (i > -1)
            {
                _SelectedA = ListA[i];
            }
            OnPropertyChanged(nameof(SelectedA));
            i = ListB.IndexOf(CurrentB);
            if (i > -1)
            {
                _SelectedB = ListB[i];
            }
            OnPropertyChanged(nameof(SelectedB));

            break;
    }
}

What we do here is basically save the current selected item for each picker on a separate variable, copy the BaseList into the two pickers that didn't call the event, then on the each new list remove all the options in use by the other pickers, set again the selected Item on each new list to the one that was selected originally and finally call OnPropertyChanged() to inform the views of the change.

The issue here is that when we change the ItemSource on a Picker it sets the SelectedItem to null. calling OnPropertyChanged() on the setter after OnSelectedItemChanged()was called leads to an infinite loop of one Picker updating the other, and adding a filter that checks if the value isn't null before setting it makes the Picker display no selected item, while the value is already set.

1 Answers

just in case anyone has the same issue, we found a solution for this. Turns out if you make CurrentA, CurrentB and CurrentC global variables and add on each case an if ((CurrentA != SelectedA) && (!(SelectedA is null))) { ... (do all the stuff) } break; and at the end you set

_SelectedA = CurrentA;
OnPropertyChanged(nameof(SelectedA));
_SelectedB = CurrentB;
OnPropertyChanged(nameof(SelectedB));
_SelectedC = CurrentC;
OnPropertyChanged(nameof(SelectedC));

it works. We don't know why tho :)

Related