Displaying a ListView item from Page1 on Page2 – Part 8 and Final – Writing a Xamarin.Forms app

Just joining the series? Here’s a recap:

Welcome to the grand finale!

Whenever I try to go down the MVVM path, I always get stuck at “How do I get PageOneViewModel and/or Page1.xaml to know about the list of Fabrics?” For example, suppose I want my Page1.xaml to show the Name of the current Fabric and its corresponding seconds. How does Page2 get the Fabric data from Page1? Fortunately, this final blog post answers this question!

Recall how my #1 biggest issue was thinking the ObservableCollection should be the “source of truth” – meaning the app pulls its fabric data requirements from the ObservableCollection (which is wrong and don’t do this). In the past, I’d achieve this by making the ObservableCollection a global variable and other things I’d brute force or SO cut and paste.

But now we have this data service. Since none of my viewmodels are instantiating the data service (my #2 biggest issue blocking me from understanding MVVM), it’s fair game for my other viewmodels to use the data service as well.

To follow along, view this diff from the previous blog post.

I have a question!!!!

  • I’m not sure why I have to specify public Fabric CurrentFabric { get => currentFabric; set => SetProperty(ref currentFabric, value); } I figured the BaseViewModel took care of all things INotifyPropertyChanged related. I also thought Mode=TwoWay would work. My only guess is this is the first time in my app thus far that the code has changed the property and not the user input, so extra stuff is needed that BaseViewModel can’t do.

Getting a Fabric to display in PageTwo

First, let’s add a Fabric property to PageTwoViewModel to databind. We’ll talk about where we get this fabric from in a bit.

public Fabric CurrentFabric { get; private set; }

inside the PageTwoViewModel constructor, let’s grab a Fabric from the data service. Just like last 2 blog posts, let’s write our “object model” first, and then implement it later.

CurrentFabric = FabricsDataService.GetFabric(0);

Next, let’s update the Page2.xaml

    <ContentPage.Content>
        <StackLayout Padding="20" HorizontalOptions="Center">
            <Label Text="{Binding CurrentFabric.Name}" FontSize="20" />
            <Label Text="{Binding CurrentFabric.Seconds}" FontSize="20" />
        </StackLayout>
    </ContentPage.Content>

And in the FabricsDataService, add a GetFabric method

        public static Fabric GetFabric(int id) {
            return Application.Current.Properties[id.ToString()] as Fabric;
        }

And let’s run it.

And what the…. we crash on launch??

Exception throws on launch

Looking at the callstack, it’s when we try to get the first fabric. There is no first fabric on first launch…

Turns out I was wrong in a prior post. For a tabbed control, both tab pages are instantiated at launch and are *not* re-instantiated each time you tab to them.

This means we need a way to handle first launch. In Page2.xaml.cs code-behind, we’ll add a

        protected override void OnAppearing()
        {
            base.OnAppearing();

            pageTwoViewModel.InitializeForFirstRun();
        }

and in the PageTwoViewModel, let’s only try to get a fabric if the count is > 0.

        internal void InitializeForFirstRun()
        {
            if (FabricsDataService.GetAllFabrics().Count > 0)
            {
                CurrentFabric = FabricsDataService.GetFabric(0);
            }
        }

Now when you run, after you add a fabric, you’ll see it appear in Page2!!!!

Let’s go one step further.

Let’s add a command to go to the next fabric (provided you’ve added two).

You’ve seen how to add a command in previous posts, so here’s the delegate in PageTwoViewModel.cs

        public void NextFabric() {

            CurrentFabric = FabricsDataService.GetFabric(1);

        }

Like I said, make sure you have at least 2 fabrics in this list! This is just to demo calling out to the FabricsDataService!!!

Now I thought the BaseViewModel would handle all updates made to CurrentFabric if we specified TwoWay binding, but it doesn’t and I’m still not sure why. You still need to add the following to PageTwoViewModel

        private Fabric currentFabric;
        public Fabric CurrentFabric { get => currentFabric; set => SetProperty(ref currentFabric, value); }

Now the CurrentFabric changes will be picked up!

That’s it for this series!

At least for now. My goal was to explain how one page in a tabbed control could get access to another page’s data-bounded items in a ListView, which we now know is the wrong question. The more accurate question is how to use a “DataService” to get access to a ListView from a different tabbed page.

Where I’d like to go next:

  • How might I add unit testing to my app? If you have a link or an example, please let me know! I should be able to test my services and my ViewModels, right?
  • How might dependency inject help me? Is this a requirement for unit testing or do I have enough to add unit tests?

 

Using App.Properties as source of truth – Part 7 – Writing a Xamarin.Forms App

Continuing from last post, we need that “source of truth” for the real Fabric list that the FabricDataService uses to perform CRUD operations.

To follow along, check out the diff from last post here.

Matt Soucoup has a great write up on using App.Properties in Xamarin.Forms.  App.Properties will be my “source of truth” for my service. I could also use the Settings plugin, but to keep this blog post focused on what the service is doing, I’m going to stick with App.Properties for this blog post.

Note: App.Properties is supposed to persist data across installs (i.e. debugging builds), but I’m running into this issue.  I’ve tried doing await Application.Current.SavePropertiesAsync() but still didn’t persist the fabrics. Moving forward, I’ll switch to the Settings plugin because I don’t want to recreate these fabrics every deploy.

What I learned

  • Oh yeah, every time you tab to a new page or open a new page, that page is being created for the first time. I have no idea why I’m so paranoid about saving memory and thinking I need to keep cached versions of these pages around all the time.
  • The Settings plug is actually quite simple as well, but for this demo, I wanted to use as few lines of code as possible to demonstrate the service, not the underlying save/persistence mechanism.
  • Think of App.Current.Properties as a way of getting the Fabrics from “storage” whether that is from disk (which is what this is doing) or from something else like Settings plugin, or from a cloud service.
  • It’s okay that every time you need to get the list of fabrics, it pulls it from “storage”. I have no idea why I want to cache everything so badly.

In the FabricDataService, we now have for GetAllFabrics()

        public static List<Fabric> GetAllFabrics()
        {

            List<Fabric> fabrics = new List<Fabric>();

            if (Application.Current.Properties.Count == 0)
            {
                return fabrics;
            }

            for (int i = 0; i < Application.Current.Properties.Count; i++)
            {
                var fabric = Application.Current.Properties[i.ToString()] as Fabric;
                fabrics.Add(fabric);
            }

            return fabrics;
        }

And AddFabric() becomes

        public async static void AddFabric(Fabric fabric) {

            fabric.Id = Application.Current.Properties.Count;
            Application.Current.Properties.Add(fabric.Id.ToString(), fabric);
            await Application.Current.SavePropertiesAsync();
        }

Next, we have to add UpdateFabric() to the data service for when the user edits an item in the ListView.

        public static void UpdateFabric(Fabric fabric)
        {
            Xamarin.Forms.Application.Current.Properties.Remove(fabric.Id.ToString());

            Xamarin.Forms.Application.Current.Properties.Add(fabric.Id.ToString(), fabric);
        }

Lastly, have DoneEditing() call the DataService.  

        private async void DoneEditing()
        {
            if (isNew) {

               FabricsDataService.AddFabric(this.Fabric);

                MessagingCenter.Send<ItemDetailsViewModel, Fabric>(this, "added", this.Fabric);
            } else {

               FabricsDataService.UpdateFabric(this.Fabric);

            }

            await Navigation.PopAsync();
        }

Now when you run the app, you’ll notice no obvious change in functionality. You can add fabrics and tab between the two pages just as before which is a good test. One thing I had to remind myself is that every time you open a page (whether navigating between two pages or opening the ItemDetailsPage), you are recreating a new instance of the View and ViewModel.

Sara can has DataService – Part 6 – Writing a Xamarin.Forms app

Just joining my series? Check out my first blog post in this series for an overview on what I’m building.

To follow along, here’s the diff from last blog post.

What I learned

  • The term “service” is a naming convention. There’s no special library you have to install to make a class act as a “service”.
  • Not everything has to be instantiated. Yeah this is the first time I’ve been forced to think this way. You don’t have one of your view models or your main app class instantiate your service. Use either a singleton or a static class.
  • Global variables are not good form. I can’t stop my brain from wanting to do a List somewhere off of the main App as the source of truth. Let the data service handle this. It will make more sense in this blog post and the next post.

Creating a FabricsDataService

First, create a new folder called Services and add a blank C# class there called FabricsDataService

Back in the days where I was a developer (SDET) on the VS team (LONG time ago), I remember the advice I got from a developer reviewing my code. He said, “Think about writing your Object Model from the point of view of the caller. Start by writing your test cases, thinking about what methods you wish were already available, and then go implement those methods.” This conversation might have been my first introduction to HCI / UX. 

Writing a service is kinda like writing that object model ages ago. Start with what methods you wish were available to you, and then go implement them. For example, in PageOneViewModel.cs in the constructor, I’m creating fabric objects just to populate the ObservableCollection.

In GitHub, press y to get a permalink to a specific commit; otherwise, if you use master and it gets updated, your link might not make sense moving forward.

For example, I’m doing

        public PageOneViewModel(INavigation Navigation)
        {

            OCFabrics = new ObservableCollection<Fabric>();

            OCFabrics.Add(new Fabric("fabric1", 5));
            OCFabrics.Add(new Fabric("fabric2", 5));
            OCFabrics.Add(new Fabric("fabric3", 5));

but my ViewModel doesn’t care what the Fabrics are or where they come from (memory, disk, the cloud?). It just wants to get all the fabrics.

You can rewrite this as

var allFabrics = FabricsDataService.GetAllFabrics();

The MVVMHelpers also has an ObservableRangeCollection that gives you extra methods like ​AddRange() since ObservableCollection only has Add() so you end up writing less code.

Now we can do

var allFabrics = FabricsDataService.GetAllFabrics();
OCFabrics.AddRange(allFabrics);

Now all we have to do is implement GetAllFabrics().

Introducing my biggest mental hurdle to MVVM

Okay here’s my biggest mental hurdle… what is FabricsDataService? I mean what sort of class is this? Where do I “new it up” or instantiate it? Turns out you’d either want to use a singleton class design pattern or just a static class. Since I’m learning to crawl towards MVVM, I’m going with static class since it is the easiest for me to conceptualize.

My next question is “where is this list of Fabrics stored in memory for other parts of my app to use?” Turns out it isn’t stored anywhere per-se. When my Page2 needs it, that page will simply called the data service. It seems redundant to me to keep new’ing up a List of fabrics on demand, especially if those are coming from disk or a web service. But I’ve had several people advise me to get past this idea that I need an app-wide List as the source of truth and instead rely on a (Fabric)DataService. I guess it’s the difference between fast food (my app-wide List vs “made to order” DataService. But then again, I remember being told all throughout my professional development days global variables are bad… so hello FabricDataService!

First, make FabricDataService a static class

public static class FabricDataService

and remove the default constructor (since it’s static).

Now back to our GetAllFabrics()… where do we get the data from? Since this is just about using the FabricDataService, we’re going to just new up those 3 fabric objects, e.g.

public static List<Fabric> GetAllFabrics()
        {

            List<Fabric> fabrics = new List<Fabric>();

            fabrics.Add(new Fabric("fabric1", 5));
            fabrics.Add(new Fabric("fabric2", 5));
            fabrics.Add(new Fabric("fabric3", 5));

            return fabrics;
        }

And now let’s rock on and hit Run (or whatever F5 translates to a Mac). And we get…

Fabrics displayed in ListView coming from DataService

Boom goes the dynamite!

But what about adding? Since we’re using ItemDetailsPage for both editing and adding, we have to check whether we’re editing a new item and respond accordingly. For example, in ItemDetailsViewModel,

        private async void DoneEditing()
        {
            if (isNew) {

                FabricsDataService.AddFabric(this.Fabric);

                MessagingCenter.Send<ItemDetailsViewModel, Fabric>(this, "added", this.Fabric);
            } else {

                FabricsDataService.UpdateFabric(this.Fabric);

            }

            await Navigation.PopAsync();
        }

Which now begs the question… what is the FabricDataService updating? We already said we didn’t want to use some app-wide global List as our source of truth. So what is the source of truth for the DataService????

This question is also known as my #2 biggest hurdle learning MVVM, which I’ll address in my next blog post: Application.Current.Properties versus Settings plugin.

Adding items in a List View – Part 5 – Writing a Xamarin.Forms app

 

Last blog post discussed editing items. This blog post uses the MessagingService in Xamarin.Forms to tell the ObservableCollection to add a new item.

To follow along, here’s the diff for this post.

What I learned

  • It’s okay to use the MessagingCenter even in a MVVM pattern. At first I didn’t think this was correct, even though the MessagingCenter appears in the Xamarin documentation under MVVM for adding a new item on a ListView. Obviously I have trust issues.
  • The observable collection updates when its .Add() is called.

Add a footer in the Page1.xaml for the Add button

               <ListView.Footer>
                    <ContentView>
                        <Button Text="Add Fabric" FontSize="Large" Command="{Binding AddFabricCommand}" HorizontalOptions="Center" VerticalOptions="CenterAndExpand" />
                    </ContentView>
                </ListView.Footer>

Then in the PageOneViewModel, add the command property

public ICommand AddFabricCommand { get; private set; }

and in the constructor, create a command to bind to.

AddFabricCommand = new Command(AddFabric);

and now finish up with the delegate

        private async void AddFabric()
        {

            Fabric fabric = null;
            await this.Navigation.PushAsync(new ItemDetailsPage(fabric));
        }

Now we need to update the ItemDetailsViewModel constructor to handle a new fabric, i.e. if a fabric passed in is null

first we need a property to know if we’re adding  a new fabric or editing an older one.

private bool isNew = false;

now for the constructor

public ItemDetailsViewModel(INavigation Navigation, Fabric fabric)
        {
            isNew = fabric == null;

            if (isNew)
            {
                this.Fabric = new Fabric(Name: "", Seconds: 60);
                Title = "Hello from new item";
            }
            else
            {
                this.Fabric = fabric;
                Title = "Hello from Item " + Fabric.Name;
            }

            this.Navigation = Navigation;
            DoneEditingCommand = new Command(DoneEditing);
        }

Now run the app. You’ll see the familiar ItemDetailsPage appear for the Add, but nothing changes in the ListView. What’s going on?

where's fabric 4

This is because the OCFabric.Add() is never called, so the ObservableCollection can’t add the new Fabric.

Where MessagingService steps in…

When we’re done editing, we need to know whether this is a new fabric or editing an older one so the ObservableCollection can add it.

        private async void DoneEditing()
        {
            if (isNew) {
                MessagingCenter.Send<ItemDetailsViewModel, Fabric>(this, "added", this.Fabric);
            }

            await Navigation.PopAsync();
        }

and now for the receiver. We’re going to subscribe to the message in the PageOneViewModel constructor.

            MessagingCenter.Subscribe<ItemDetailsViewModel, Fabric>(this, "added", (sender, arg) =>
            {
                OCFabrics.Add(arg);
            });

and now Fabric4 is added to the ListView

TheresFabric4.png

Note: yeah I know there’s a more “pure” way to use the MessageCenter by creating a class called FabricSaveMessage as the sender, but it hasn’t really clicked yet why I’d want to create another class. Perhaps further on as I develop this app I’ll run into a good reason for not using the ViewModels as the sender and receiver for these messages.

Alrighty! Next blog post begins my real lightbulb moments using MVVM – how to use a “service” to handle being the “source of truth” for the fabric list.