4

I am kinda new to Xamarin and developing an iOS app, I need to call an API and bind the response data to view have used MVVM pattern.

Here is my ViewModel Code:

public class PersonalDetailModel : BaseViewModel
{
    private PersonalDetails personalDetails { get; set; }
    public Command LoadCommand;

    public PersonalDetailModel()
    {
        LoadCommand = new Command(async () => await GetPersonalDetais());

    }
    public String City
    {
        get
        {
            return personalDetails.city;
        }
        set
        {
            personalDetails.city = value;
        }
    }
    public string Phone
    {
        get { return personalDetails.phone; }
        set
        {
            personalDetails.phone = value;

        }
    }
    public string Email
    {
        get { return personalDetails.email; }
        set
        {
            personalDetails.email = value;

        }
    }

    public async Task<PersonalDetails> GetPersonalDetais()
    {
        var ApiRequest = RestService.For<IUserService>(Constants.BaseUrl);

        PersonalDetailsResponse ApiResponse = await ApiRequest.showProfile(Constants.token);

        PersonalDetailsResponse response = ApiResponse;

        this.personalDetails = response.result;
        personalDetails = new PersonalDetails
        {
            city = personalDetails.city,
            phone = personalDetails.phone,
            email = personalDetails.email
        };

        return this.personalDetails;
    }


}

In GetPersonalDetais() I am getting the values from API in debug process but I am not able to bind that value in UI.

XML Code:

<?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="BarberCustomer.Views.PersonalDetails"
             >
    <ContentPage.Content>
        <StackLayout Orientation="Vertical" Padding="4,5" BackgroundColor="#efeff4" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand">
            <Frame HasShadow="True" Padding="8,5,8,12" CornerRadius="2"  Margin="1,1,0,0" HorizontalOptions="FillAndExpand" 
                   VerticalOptions="Start">
                <StackLayout Orientation="Vertical" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand">
                    <Label Text="Personal Details" HorizontalOptions="Start" VerticalOptions="FillAndExpand" LineBreakMode="WordWrap" FontSize="12" TextColor="#ad0e0e">
                        <Label.FontFamily>
                            <OnPlatform x:TypeArguments="x:String">
                                <OnPlatform.Android>SFProDisplay-Bold.ttf#SF-Pro-Display-Bold</OnPlatform.Android>
                                <OnPlatform.iOS>SFProDisplay-Bold</OnPlatform.iOS>
                            </OnPlatform>
                        </Label.FontFamily>
                    </Label>
                    <StackLayout Margin="0,1,0,1" HorizontalOptions="FillAndExpand" HeightRequest="1" BackgroundColor="#efeff4" VerticalOptions="EndAndExpand" Padding="0">
                    </StackLayout>
                    <StackLayout Orientation="Horizontal" HorizontalOptions="FillAndExpand" VerticalOptions="Center">
                        <Image Source="location.png" WidthRequest="8" HeightRequest="11" VerticalOptions="Center" Aspect="AspectFit" ></Image>
                        <Label Text="{Binding City}" Margin="4,0,0,0"
                               HorizontalOptions="Start" VerticalOptions="FillAndExpand" LineBreakMode="WordWrap" FontSize="9" TextColor="#253045">
                            <Label.FontFamily>
                                <OnPlatform x:TypeArguments="x:String">
                                    <OnPlatform.Android>SFProDisplay-Medium.ttf#SFProDisplay-Medium</OnPlatform.Android>
                                    <OnPlatform.iOS>SFProDisplay-Medium</OnPlatform.iOS>
                                </OnPlatform>
                            </Label.FontFamily>
                        </Label>

                    </StackLayout>

                    <StackLayout HorizontalOptions="FillAndExpand" HeightRequest="1" BackgroundColor="#eeeeee" VerticalOptions="EndAndExpand" Padding="0" Margin="17,1,0,1">

                    </StackLayout>
                    <StackLayout Orientation="Horizontal" HorizontalOptions="FillAndExpand" VerticalOptions="Center">
                        <Image Source="phone.png" WidthRequest="8" HeightRequest="11" VerticalOptions="Center" Aspect="AspectFit" ></Image>
                        <Label Text="{Binding Phone}" Margin="4,0,0,0"
                               HorizontalOptions="Start" VerticalOptions="FillAndExpand" LineBreakMode="WordWrap" FontSize="9" TextColor="#253045">
                            <Label.FontFamily>
                                <OnPlatform x:TypeArguments="x:String">
                                    <OnPlatform.Android>SFProDisplay-Medium.ttf#SFProDisplay-Medium</OnPlatform.Android>
                                    <OnPlatform.iOS>SFProDisplay-Medium</OnPlatform.iOS>
                                </OnPlatform>
                            </Label.FontFamily>
                        </Label>
                    </StackLayout>
                    <StackLayout HorizontalOptions="FillAndExpand" HeightRequest="1" BackgroundColor="#eeeeee" VerticalOptions="EndAndExpand" Padding="0" Margin="17,1,0,1">

                    </StackLayout>
                    <StackLayout Orientation="Horizontal" HorizontalOptions="FillAndExpand" VerticalOptions="Center">
                        <Image Source="mail.png" WidthRequest="11" HeightRequest="12" VerticalOptions="Center" Aspect="AspectFit" ></Image>
                        <Label Text="{Binding Email}" Margin="4,0,0,0"
                               HorizontalOptions="Start" VerticalOptions="FillAndExpand" LineBreakMode="WordWrap" FontSize="9" TextColor="#253045">
                            <Label.FontFamily>
                                <OnPlatform x:TypeArguments="x:String">
                                    <OnPlatform.Android>SFProDisplay-Medium.ttf#SFProDisplay-Medium</OnPlatform.Android>
                                    <OnPlatform.iOS>SFProDisplay-Medium</OnPlatform.iOS>
                                </OnPlatform>
                            </Label.FontFamily>
                        </Label>
                    </StackLayout>
                </StackLayout>
            </Frame>
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

xml.cs:

[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class PersonalDetails : ContentPage
{
    PersonalDetailModel viewModel;

    public PersonalDetails()
    {
        InitializeComponent();

        viewModel = new PersonalDetailModel();

        BindingContext = viewModel;



    }

    protected async override void OnAppearing()
    {
        base.OnAppearing();
        viewModel.LoadCommand.Execute(null);

    }
}

Every response and suggestions will be appreciated!

SMM
  • 149
  • 7
  • Where is your button on which you bind the data?? Is it in another view? Or are you trying to to bind the data in the same view with your button? – Idev Dev Jun 29 '18 at 13:59
  • @IdevDev on the same view – SMM Jun 29 '18 at 14:02
  • Is this xml all you have? Can you please share the xaml.cs too? So we can have a better understanding of the situation? – Idev Dev Jun 29 '18 at 14:08
  • @IdevDev see my edits! – SMM Jun 29 '18 at 14:21
  • @SMM, How did you separate API call from ViewModel? I couldn't understand from the line *var ApiRequest = RestService.For(Constants.BaseUrl);* – Thamarai T Nov 18 '20 at 07:29
  • I have separate folders for Views, Models & ViewModels. Now I need to separate REST API calls from ViewModels. But I don't know how. – Thamarai T Nov 18 '20 at 07:34

2 Answers2

3

You should put BindingContext either in the xaml.cs or in the xaml to bind your ViewModel with the View.

There are multiple ways to bind the data to your View.

ViewModel.cs

public class PettyCashListViewModel : BaseNavigationViewModel

Page.Xaml.cs

public partial class PettyCashListPage : ContentPage
{
    protected PettyCashListViewModel ViewModel => BindingContext as PettyCashListViewModel;

or Inside your page constructor

this.BindingContext = ViewModel;

or Inside your page.xaml

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
         xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
         xmlns:viewModels="clr-namespace:XamarinPOC.ViewModel; assembly=XamarinPOC.ViewModel"
         x:Class="XamarinPOC.Summary"
         Title="Summary List">
     <ContentPage.BindingContext>
        <viewModels:SummaryViewModel/>
     </ContentPage.BindingContext>
     <StackLayout>
        <Label Text="{Binding test}"/>
     </StackLayout>
   </ContentPage>

Reference: Set BindingContext to ViewModel in XAML on Xamarin.Forms

Selvarathinam Vinoch
  • 1,080
  • 3
  • 13
  • 23
1

I second Selvarathinams answer. Also make sure, your model implements the INotifyPropertyChanged interface.

You should add

public event PropertyChangedEventHandler PropertyChanged;

and

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

to your model class (unless it is already implemented in the base class your model derives from). Then in all setters call NotifyPropertyChange("propertyName).

For instance it would be:

public string Email
{
    get { return personalDetails.email; }
    set
    {
        personalDetails.email = value;
        NotifyPropertyChanged("Email");
    }
}

This will ensure that your view gets notified that a model value has changed.

Also try updating your model values in your UI thread. In order to do so, where you handle the result from your async method, do the following:

Xamarin.Forms.Device.BeginInvokeInMainThread( () => {
    model.City = personalDetails.city;
    model.Phone = personalDetails.phone;
    model.Email = personalDetails.email;
}

if that shouldn't work, you might change the method in OnAppearing a bit:

protected async override void OnAppearing()
{
    base.OnAppearing();
    PersonalDetails details = await model.GetPersonalDetais();
    Xamarin.Forms.Device.BeginInvokeInMainThread( () => {
        model.City = personalDetails.city;
        model.Phone = personalDetails.phone;
        model.Email = personalDetails.email;
    }
}

Speaking of, I personally find it a bit strange that the method to retrieve the data is part of your model class. It would implement such a method inside of a static class called DataService, which holds all the methods used to retrieve data and keep the model clean so that it only contains the data needed to be bound to the view.

Markus Michel
  • 2,289
  • 10
  • 18