0

I'm very new to Xamarin.Forms and MVVM and posting questions here on StackOverflow so bear with me please. I'm trying to fill a listview in Xamarin.Forms. I first programmed it without MVVM and it all worked like I wanted it to, but now I wanted to get in in MVVM and that is where it went wrong, now my list won't fill up anymore. I made a viewmodel and only put all the binding in the viewmodel, I have not yet implemented eventhandlers.

This is a part of the code behind (I have a couple more eventhandlers but that is not relevant right now):

    namespace CCXamarinApp
    {
        [XamlCompilation(XamlCompilationOptions.Compile)]
        public partial class PatientsWithTagPage : ContentPage
        {
            public PatientsWithTagPage()
            {
                BindingContext = new PatientsWithTagViewModel();

                InitializeComponent();
                (BindingContext as PatientsWithTagViewModel).GetAllPatients();

                if((BindingContext as PatientsWithTagViewModel).IsEmptyPatientList)
                    HandleEmptyList();
                else
                    (BindingContext as PatientsWithTagViewModel).SortAndShowPatients();
            }

            private void SearchBar_OnTextChanged(object sender, TextChangedEventArgs e)
            {
            (BindingContext as PatientsWithTagViewModel).Searching(e.NewTextValue);
            }
           ...

This is my XAML page:

        <SearchBar x:Name="SearchBar" Placeholder="Zoek op naam of plaats..." HeightRequest="25" Margin="10"
                TextChanged="SearchBar_OnTextChanged"/>

        <Label Text="{Binding LastRefreshed}" FontAttributes="Italic" FontSize="15" />

        <Label x:Name="LabelEmptyList" FontSize="17" Text="Geen gegevens gevonden" FontAttributes="Bold"
                IsVisible="False" />

        <ListView x:Name="PatientListView" HasUnevenRows="True" SeparatorColor="Accent"
                VerticalOptions="FillAndExpand" IsPullToRefreshEnabled="True" IsRefreshing="{Binding IsFetchingData, Mode=TwoWay}"
                Refreshing="PatientListView_OnRefreshing" SelectedItem="{Binding SelectedPatient, Mode=TwoWay}"
                ItemSelected="PatientListView_OnItemSelected" ItemsSource="{Binding Patients}"
                  IsVisible="True" BackgroundColor="Aqua">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <StackLayout Spacing="4">
                            <StackLayout Orientation="Horizontal" Margin="10,7,10,1">
                                <Label Text="{Binding FullName}" FontAttributes="Bold" FontSize="16" />
                                <Label Text="{Binding DisplayTimeOfLastScan, StringFormat='{0}'}"
                                        HorizontalOptions="EndAndExpand" />
                            </StackLayout>
                            <StackLayout Orientation="Horizontal" Margin="10,0,10,7">
                                <Label Text="{Binding LastLocation}" HorizontalOptions="Start" />
                                <Label Text="{Binding DisplayDurationSinceLastScan, StringFormat='al {0}'}"
                                        HorizontalOptions="EndAndExpand" />
                            </StackLayout>
                        </StackLayout>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </StackLayout>
</ContentPage.Content>

This is my viewmodel (not all the code but the code that is most relevant). The BaseViewModel it derives from is from the nuget package "Refractored.Mvvmhelpers":

class PatientsWithTagViewModel : BaseViewModel
    {
    public ObservableCollection<PatientViewModel> Patients { get; private set; } = new ObservableCollection<PatientViewModel>();

    private PatientViewModel selectedPatient;
    public PatientViewModel SelectedPatient
    {
        get => selectedPatient;
        set => SetProperty(ref selectedPatient, value);
    }

    private readonly JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings
    {
        DateFormatString = "dd-MM-yyyTH:mm",
        DateTimeZoneHandling = DateTimeZoneHandling.Utc,
    };

    public bool IsEmptyPatientList => Patients.Count == 0;

    private string testJson = "[{\"firstName\":\"P.\",\"lastName\":\"Selie\",\"tag\":{\"tagId\":\"124\",\"tagSerialNumber\":\"ABC135\"},\"scans\":[{\"location\":\"Tuin\",\"dateTime\":\"May01,2018,10:10\"},{\"location\":\"Eetzaal\",\"dateTime\":\"May02,2018,10:15\"},{\"location\":\"Gang\",\"dateTime\":\"May02,2018,11:10\"},{\"location\":\"Kamer23\",\"dateTime\":\"May02,2018,12:09\"}],\"id\":\"dcc4fe9929b3681f\"}," +
                              "{\"firstName\":\"W.\",\"lastName\":\"Janssens\",\"tag\":{\"tagId\":\"132\",\"tagSerialNumber\":\"ABC167\"},\"scans\":[{\"location\":\"Kamer23\",\"dateTime\":\"May01,2018,23:39\"},{\"location\":\"Gang\",\"dateTime\":\"May02,2018,04:10\"},{\"location\":\"Eetzaal\",\"dateTime\":\"May02,2018,04:11\"},{\"location\":\"Gang\",\"dateTime\":\"May02,2018,04:20\"},{\"location\":\"Kamer23\",\"dateTime\":\"May02,2018,04:22\"}],\"id\":\"a6dac28475327922\"}]";

    public void GetAllPatients()
    {
        IsFetchingData = true;
        try
        {
            Patients = new ObservableCollection<PatientViewModel>(
                JsonConvert.DeserializeObject<ObservableCollection<PatientViewModel>>(testJson,
                    jsonSerializerSettings));
        }
        catch(Exception e)
        {
            Console.WriteLine("*****ERROR kon API niet ophalen");
            Console.WriteLine(e.Message);
        }
        finally
        {
            IsFetchingData = false;
        }
    }

This is the model I use:

public class Patient : ObservableObject
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public Tag Tag { get; set; }
        public List<Scan> Scans { get; set; }
        public string Id { get; set; }

        public override string ToString()
        {
            return LastName + ", " + FirstName;
        }

    }

It also has its own viewmodel with properties like DisplayDurationSinceLastScan, but I don't think it is relevant here, if you think it is, let me know.

So with this code I get my page but there seems to be no items in the list, if I debug, Patients is filled with items so it is not empty at all, but something goes wrong with the binding I guess but no error is given.

Here is a picture of what I get: the listview is shown (I added a blue background so I would know if the listview was visible or not), but there are no items in there. Still Patients is filled when I debug the app.

Does someone see the mistake I made?

Marijke
  • 153
  • 3
  • 14

1 Answers1

0

I see some issues with your code here from a maintainability point of view. Try to keep all your code inside the view model instead of both the view model and code behind. Ideally, your code behind contains nothing and if anything it strictly has to do with visual things.

Anyway, in regard to you problem: you are creating a new ObservableCollection each time. That breaks the binding. Just keep the new ObservableCollection at the top and then when new data comes in, clear that and repopulate it. Like this:

public void GetAllPatients()
{
    IsFetchingData = true;
    try
    {
        var resultPatients =         JsonConvert.DeserializeObject<ObservableCollection<PatientViewModel>>(testJson, jsonSerializerSettings);

        Patients.Clear(); 

        foreach (var patient in resultPatients)
            Patients.Add(patient);
    }
    catch(Exception e)
    {
        Console.WriteLine("*****ERROR kon API niet ophalen");
        Console.WriteLine(e.Message);
    }
    finally
    {
        IsFetchingData = false;
    }
}

Your data should now show up.

Gerald Versluis
  • 30,492
  • 6
  • 73
  • 100
  • Thanks, this worked! You have a good point about my code behind, but I tried to point it out that I was not finished with implementing MVVM, but apparently I was not clear enough :) So I am well aware that my code behind needs some refactoring, thanks anyway for pointing it out! – Marijke May 07 '18 at 10:20
  • Now I know why I did new ObservableCollection: If i use linq and then fill the listview and cast it to observablecollection I get "System.InvalidExcpetion: Specified cast is not valid". That is the reason why I put new ObservableCollection because that didn't give any errors. Any idea how I can fix this? – Marijke May 07 '18 at 10:32
  • I wasn't trying to be smart, indeed just pointing out things can be improved to put you on the right track, but glad to hear you're on it! Let's take your next question offline. Can you reach out to me directly at gerald@verslu.is? We can just speak Dutch then as well ;-) – Gerald Versluis May 07 '18 at 11:00