Search code examples
c#xamarinmvvmxamarin.formsmodels

Bind data collection on xamarin using mvvm


So I started learning xamarin and trying different approaches about data binding from the view to the model. I'm retrieving data from a service using post request and after I get the data I can't bind them to the view. After a lot of research I found some interesting solutions and I tried different approaches. This what I have achieved until now : My view :

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
         xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
         x:Class="Ideas.Pages.IdeasSinglePage"
         xmlns:vm="clr-namespace:Ideas.ViewModel;assembly=Ideas"
          Title="My idea">

<ContentPage.BindingContext>
    <vm:IdeasViewModel/>
</ContentPage.BindingContext>

<StackLayout>
    <Button Command="{Binding GetIdeasCommand}"
            Text="Bileta Ime"
            TextColor="White"
            FontSize="15"
            BackgroundColor="#29abe2"/>
    <Label Text="test"></Label>

    <ListView ItemsSource="{Binding Ideas}"
              HasUnevenRows="True">

        <ListView.ItemTemplate>
            <DataTemplate>
                <ViewCell>
                    <StackLayout Padding="20, 10">
                        <Label  Text="{Binding IdeasName}"
                                 FontSize="16"
                               TextColor="RoyalBlue"/>

                    </StackLayout>
                </ViewCell>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>

</StackLayout>

This is my viewmodel

public class IdeasViewModel : INotifyPropertyChanged
{
    ApiServices _apiServices = new ApiServices();
    public List<Ideas> Ideas
    {
        get { return Ideas; }
        set
        {
            Ideas= value;
            OnPropertyChanged();
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public ICommand GetIdeasCommand
    {
        get
        {
            return new Command(async () =>
            {
                Ideas= await _apiServices.GetIdeasAsync();
            });
        }
    }


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

}

This is my service :

public async Task<List<Ideas>> GetIdeasAsync()
    {
        ListIdeasDetails ideas= null;
        try { 
            var client = new HttpClient();
            client.DefaultRequestHeaders.Add("parameter", "parameter");
            client.DefaultRequestHeaders.Add("parameter", parameter);

            HttpContent content = new StringContent("");
            content.Headers.ContentType = new MediaTypeHeaderValue("application/json");

            var response = await client.PostAsync("https://heregoes/themethod", content);
            response.EnsureSuccessStatusCode();
            string json = await response.Content.ReadAsStringAsync();
            ideas= JsonConvert.DeserializeObject<ListIdeasDetails>(json);
        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex.Message.ToString());
        }
        return ideas.Ideas;
    }
}

And here are my two models :

public class Ideas
{

    public string IdeasID  { get; set; }

    public string IdeasName  { get; set; }
 }


class ListIdeasDetails
{
    public List<Ideas> Ideas{ get; set; }
    public string ExceptionMessage { get; set; }
    public bool HasException { get; set; }

}

I would really appreciate some help! Thanks!!


Solution

  • Seems like you have a problem with your property definition:

     public List<Ideas> Ideas
     {
        get { return Ideas; }
        set
        {
            Ideas= value; // Endless loop
            OnPropertyChanged();
        }
     }
    

    Add a backing field this way:

    List<Ideas> _ideas;
    public List<Ideas> Ideas
    {
        get { return _ideas; }
        set
        {
            if(value == _ideas) return;
            _ideas = value;
            OnPropertyChanged();
        }
    }
    

    I would recommend to use Fody.PropertyChanged in order to reduce the amount of boiler-plate code related to INotifyPropertyChanged. Using Fody your ViewModel will look as simple as:

    public class IdeasViewModel : INotifyPropertyChanged
    {
        ApiServices _apiServices = new ApiServices();
        public List<Ideas> Ideas { get;set; }
    
        public event PropertyChangedEventHandler PropertyChanged;
    
        public ICommand GetIdeasCommand
        {
            get
            {
                return new Command(async () =>
                {
                    Ideas= await _apiServices.GetIdeasAsync();
                });
            }
        }
    }
    

    P.S.: Beside your main question I would like to point out the next things that does not look good in your code.

    1. Using POST to download data from WEB.
    2. Class name 'ApiServices'.

    You can let me know if you need further assistance by commenting.