Xamarin Dev Days Lab – Prism Step 3

Introduction

In our last post, we set up the view model for the SpeakersPage and made use of the built in view model functionality included in Prism. We setup a base view model for our app to contain common functionality. The SpeakersViewModel class has the implementation code for retrieving the list of speakers embedded in the view model itself. Fine for a demo, but in a real app, we would be better served by moving this functionality out and using it via an interface. By implementing the functionality as an interface, we will be able to reuse the code in other view models. Since the SpeakersPageViewModel only knows it via the interface, we will also be able to mock the service with known values for use in unit tests, without changing the view model. And we can use the Prism framework and the dependency injection that is built in to make this easier. So lets take a look at this.

The Interface

The first thing we want to do is look at the functionality that is currently in our view model class. All it is doing is calling a web api to return a list of speakers. That seems pretty straight forward. Let’s start our interface with that:


public interface ISpeakersService
{
    Task<List<Speaker> GetAllSpeakersAsync();
}

Implementing will be easy: we already have all the code that we need in the view model from the demo app:


public class SpeakersService : ISpeakersService
{
    public async Task<List<Speaker>> GetAllSpeakersAsync()
    {
        using (var client = new HttpClient())
        {
            //grab json from server
            var json = await client.GetStringAsync(
                "http://demo4404797.mockable.io/speakers");

             //Deserialize json
             var items =
                JsonConvert.DeserializeObject<List<Speaker>>(json);
             return items;
        }
    }
}

//

From above, you can see that we have just moved all of the implementation code from the view model into a separate service object. So how do we start using this new code in our SpeakersViewModel?
The first thing we need to do is register this service in our App object. Remember that we are registering via an interface: this means we can change out the implementation at any time.
Here is the RegisterTypes function in the App object updated to register the service:


protected override void RegisterTypes()
{
    Container.RegisterType
        <Services.ISpeakersService, Services.SpeakersService>();
    Container.RegisterTypeForNavigation
        <View.SpeakersPage, ViewModel.SpeakersViewModel>();
}

Next we need to refactor the SpeakersViewModel: we will add the interface to the view model constructor and change the GetSpeakersAsync to call the interface to retrieve the speakers data:


public class SpeakersViewModel : BaseViewModel
{
    private Services.ISpeakersService _speakersService = null;

    public SpeakersViewModel(Services.ISpeakersService speakersService)
        : base()
    {
        _speakersService = speakersService;
    }

    /// other parts of the view model go here
    /// now lets look at GetSpeakers ...
    private async Task GetSpeakers()
    {
        if (IsBusy)
            return;

        Exception error = null;
        try
        {
            IsBusy = true;

            var items =
                await _speakersService.GetAllSpeakersAsync();
            Speakers.Clear();

            foreach (var item in items)
                Speakers.Add(item);
        }
        catch (Exception ex)
        {
            error = ex;
        }
        finally
        {
            IsBusy = false;
        }

        if (error != null)
            await Application.Current.MainPage.DisplayAlert(
                "Error!",
                error.Message,
                "OK");
    }

}

So what do we have above? All of a sudden the ViewModel has a dependency on ISpeakersService. But we weren’t creating the view model in the first place: instead, Prism and the AutoWireViewModel attached property (see the previous blog post) are creating the view model for us and attaching it to the page. When Prism does this, it uses the container that was selected (step 01 blog) to create the view model and all the dependencies that are required for the view model, and it does this via reflection and then looking at what you specified in the App.RegisterTypes method. I hope that you can see that you can change up the implementation details of the interface without ever affecting the view model itself. You can even unit test your view model by mocking the service.

Now the view model just makes a call to retrieve the speakers. It has no idea if the speaker are read from a web api, a database, a file or even from memory! It just knows to make the call and it will get the data.

I really want to emphasize this: the view model knows nothing. It has no dependency on any implementation and you can change that around as you see fit! In fact, I am going to change the code of the SpeakersService implementation. I captured the json payload that was returned from the web api and saved it as a constant in the implementation. Now instead of calling out to the web api, I just return the json payload directly, and now for testing, everything is much quicker. And as mentioned earlier, using these known values, I can use this implementation for unit testing of the view model.


public class SpeakersService : ISpeakersService
{
    /// you can see this actual string value in the GitHub repo
    /// under step 03
    private const string JsonResponse =
        "...json response string captured from web api";

    public async Task<List<Speaker>> GetAllSpeakersAsync()
    {
        var items =
            JsonConvert.DeserializeObject<List<Speaker>>(JsonResponse);
        return items;
    }
}

Recap

We have taken the next step in making the dev days app a little more production worthy. We took out the implementation of our speakers service away from the view model, defined it as an interface and injected that into our view model. We used the dependency injection that is built into Prism to inject the implementation of the service into the view model. And then for fun (and quicker testing), we changed the implementation of the interface to serve up the results from a json payload that had been captured previously. Now we can do testing much quicker.

All of this code can be found in the Step03 portion of the repository. Make sure you check out the two different implementations of ISpeakersService contained in the SpeakersService.cs file and how they could be registered in App.cs.

The next step will be to add navigation to the app so we can see the details of a selected speaker.


Leave a comment