Search code examples
c#mvvmcrossselecteditemmvxbindmvxspinner

Binding to MvxSpinner SelectedItem property not working


I use a MvxSpinner to show country phone prefixes in a combobox in a MvvmCross for Xamarin app. I can bind to the ItemsSource property correctly, so I can see the list of my prefixes but when I assign the property in my view model that is bind to the SelectedItem property of the MvxSpinner, it won't work and will always show the first element in the list as the selected item.

The way I do it is the following. In my ViewModel I get the user data from the server and assign the properties for Country and PhonePrefix. Then I get the list of all countries and prefixes also from server and bind them to the list properties that are binded to the ItemSource properties of the respewctive MvxSpinners (simplified):

    public string PhonePrefix { get; set; }
    public string PhoneNumber { get; set; }
    public Country Country { get; set; } = new Country();

    public List<Country> Countries { get; set; } = new List<Country>();
    public List<string> Prefixes { get; set; } = new List<string>();

    private async Task GetUserData()
    {
        try
        {
            var userDataResult = await _registrationService.GetLoggedInUserData();

            if (userDataResult != null)
            {
                if (!userDataResult.HTTPStatusCode.Equals(HttpStatusCode.OK))
                {
                    Mvx.IoCProvider.Resolve<IUserDialogs>().Alert(userDataResult.Error?.Message);
                }
                else
                {

                        PhonePrefix = userDataResult.user.country_code_phone;
                        PhoneNumber = userDataResult.user.phone;
                        Country.id = userDataResult.user.person.addresses[0].country_id;
                        Country.name = userDataResult.user.person.addresses[0].country_name;
                }
            }
        }
        catch (Exception e)
        {
            Log.Error<RegistrationViewModel>("GetUserData", e);
        }
    }

    /// <summary>
    /// Populates the view model properties for Countries and Prefixes with information retrieved from the server
    /// </summary>
    private void ProcessFormData()
    {
        if (_registrationFormData != null)
        {
            Countries = _registrationFormData.Countries?.ToList();
            var userCountry = Countries?.Where(c => c.id == Country?.id).FirstOrDefault();
            Country = IsUserLogedIn && Country != null ? userCountry : Countries?[0];

            var prefixes = new List<string>();
            if (Countries != null)
            {
                foreach (var country in Countries)
                {
                    //spinner binding doesn't allow null values
                    if (country.phone_prefix != null)
                    {
                        prefixes.Add(country.phone_prefix);
                    }
                }
            }

            //we need to assign the binded Prefixes at once otherwise the binding for the ItemSource fails
            Prefixes = prefixes;
            PhonePrefix = Prefixes.Count > 0 && !IsUserLogedIn && string.IsNullOrWhiteSpace(PhonePrefix) ? Prefixes?[0] : PhonePrefix;
        }
    }

And in the axml layout:

    <MvxSpinner
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:minHeight="25dp"
            android:layout_marginTop="5dp"
            android:layout_marginBottom="10dp"
            android:id="@+id/spnrCountry"                   
            local:MvxBind="ItemsSource Countries; SelectedItem Country"/>
    <MvxSpinner
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:minHeight="25dp"
            android:layout_marginBottom="10dp"
            android:id="@+id/spnrPrefix"
            local:MvxBind="ItemsSource Prefixes; SelectedItem PhonePrefix"/>

On the output window I can see the following error regarding MvxBinding:

(MvxBind) Null values not permitted in spinner SelectedItem binding currently

I debugged and I never have any Null values in the lists or in the properties I bind to the ItemSource and SelectedItem properties of the MvxSpinner.

Actually the Countries ItemSource and SelectedItem work properly so if user saved it's country to be Argentina, when I load it's data the selected item in the spinner will be Argentina. Note that I use a Country entity like that:

public class Country
{
    public int id { get; set; }
    public bool favorite { get; set; }
    public string name { get; set; }
    public string name_de { get; set; }
    public string code { get; set; }
    public int rzl_code { get; set; }
    public string phone_prefix { get; set; }
    public string updated_at { get; set; }
    public string created_at { get; set; }

    public override string ToString()
    {
        return name;
    }
}

I also tried to make the phone prefix in it's own entity wrapping a string value but it didn't work either.

Does anybody knows what I'm doing wrong? Why for the Countries it's working and for the prefixes not?

I use PropertyChanged.Fody.

Thanks!


Solution

  • The error you are getting about null is because SelectedItem in the spinner does not allow null as a selected item. So if you want to display an empty item one solution is to add another fixed item that has an empty value, i.e. in the case of PhonePrefix you can set string.Empty and add it to the list of PhonePrefixes and in your Country you can set the first one as default or create a stub Country with name None for example and add it to the list of countries.

    Another point to take into account is that when you want to update the view you have to be sure that you are notifying it in the Main Thread. You are trying to update the PhonePrefix in a Task of another thread so the view does not get noticed.

    You should update PhonePrefix by doing:

    this.InvokeOnMainThread(() => PhonePrefix = userDataResult.user.country_code_phone; );

    This will take care of doing the set of PhonePrefix directly on the Main thread so your view will be notified correctly.


    Update

    After better looking at your question and own answer and seeing that you use PropertyChanged.Fody I can guess that the problem was in fact how you are assigning the PhonePrefix.

    PropertyChanged.Fody defaults behaviour is to add Equality Checking which replaces your property code

    public string PhonePrefix { get; set; }
    

    for something like

    private string _phonePrefix;
    public string PhonePrefix
    {
        get
        {
            return _phonePrefix;
        }
        set
        {
            if (!String.Equals(_phonePrefix, value))
            {
                _phonePrefix = value;
                OnPropertyChanged("PhonePrefix");
            }
        }
    }
    

    so when you do in the GetUserData():

    PhonePrefix = userDataResult.user.country_code_phone;
    

    and in the ProcessFormData()

    PhonePrefix = Prefixes.Count > 0 && !IsUserLogedIn && string.IsNullOrWhiteSpace(PhonePrefix) ? Prefixes?[0] : PhonePrefix;
    

    PhonePrefix is not null or whitespace so it tries to reassign the same value but because fody adds the equality checking it does not get assigned again and therefore it does not raise the change of the value, so the view does not get notified.

    The assignation in GetUserData() I think it may be being done in another thread and that's why the view does not get notified. According of what you said Country does get updated in ProcessFormData() so in order to PhonePrefix to be updated too in that place you should only add the [DoNotCheckEquality] attribute to the property to avoid the equality checking and that should be all.

    [DoNotCheckEquality]
    public string PhonePrefix { get; set; }
    

    If it does not work you should add the invocation on the main thread too (I advise you to see in debug on which thread is the method being executed to see if you do need the invoke on main thread).

    HIH