Display A Dialog From The ViewModel

In a previous blog post I discussed the problem of being in your ViewModel yet having to display a dialog to the user. You can’t interact with the page from the ViewModel, but you don’t want the logic of your program in the code-behind.

In that previous article, I demonstrated solving that problem using an event. In this blog post, Rodrigo Juarez and I will demonstrate three alternatives:

  • Using the Messaging System
  • Using Events (though here we’ll show a somewhat different approach)
  • Using dependency/service injection

There are significant problems with the first two; the most important of which is that they are difficult or impossible to write unit tests for. The third approach, however, is clean, efficient and very testable using a Mock service.

Messaging

We start with a very simple main page. It looks like this:

Main Page

This will let us test each approach in turn. Let’s start with the messaging center. The idea here is that you send a message to the messaging system, and it is received by any class that registers for it. This follows the pub/sub (publication/subscription) pattern.

In our case, we start in our view model (MessagingCenterCallAndReturnViewModel, where we send out our message like this:

Device.BeginInvokeOnMainThread(() =>
{
    MessagingCenter.Send(this, "CallViewFromViewModel");
});

The ViewModel also has a method named DoSomething() which will take whatever action the view instructs it to take.

The action is in the View. The key code is in the OnAppearing() mthod,

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

            MessagingCenter.Subscribe <MessagingCenterCallAndReturnViewModel> (this, "CallViewFromViewModel", HandleConfirmation);
        }

Notice that you are subscribing to the message by putting the name of the publishing view model into the type, and following that with the type of the receiving class (this) and the text that uniquely identifies this message (“CallViewFromViewModel). The final parameter is the name of the method you want to invoke in this class:

      public async void HandleConfirmation(MessagingCenterCallAndReturnViewModel sender)
        {
            Debug.WriteLine("Handling Call from ViewModel");
            var config = new ConfirmConfig()
            {
                Title = "Confirmation",
                Message = "Would you like to perform the operation?",
                OkText = "Yes",
                CancelText = "No",
            };
            if (await UserDialogs.Instance.ConfirmAsync(config))
            {
                Vm.DoSomething();
            }
        }

We’ll use this method a lot, so briefly: you send in the publishing view model, and you pop a dialog asking the user if they want to perform an operation. If they say yes, you call DoSomething() back in the view model!

An important part of registering for the message is unregistering, which you do, no surprise, in OnDisappearing

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

    MessagingCenter.Unsubscribe<MessagingCenterCallAndReturnViewModel>(this, "CallViewFromViewModel");
}

Event Handling

The second approach is to handle an event. In this somewhat cleaner and simpler approach, we set up an event in the ViewModel using an Action. (An action is any method that takes one parameter and returns void)

 public ICommand ExecuteSomeOperationCommand => new Command(() =>
     {
         Debug.WriteLine("Sending Message to the View");
         AskForConfirmation?.Invoke();
     });

 public Action AskForConfirmation { get; set; }

Notice that we check to see if AskForConfirmation is null, which it will be until someone registers for that event.

In the code behind for the page, we register for the event:

Vm.AskForConfirmation = async()=>
{
    await HandleConfirmation();
};

When the event is invoked, HandleConfirmation is called and presents the user with the same dialog shown above.

Service Injection

In our preferred approach, you inject the service you need into the constructor of the view model. This has the tremendous advantage that you can mock the service when creating unit tests.

In all these approaches the ViewModel is declared in the XAML,

 <ContentPage.BindingContext>
     <viewModel:EventHandlerCallAndReturnViewModel/>
 </ContentPage.BindingContext>

The XAML also contains a button which will be handled by a command handler in the ViewModel

<Button Command="{Binding ExecuteSomeOperationCommand}" 
        IsVisible="{Binding ButtonIsVisible}"  Text="Do Something"/>

In the code behind we pick up the binding context (the view model) and call Initialize it, optionally passing in a parameter,

The constructor uses the DependencyService to locate the confirmationService, and invokes the overloaded contructor, which sets a local field to hold the confirmation service and to initialize the command that was attached to a button in the Xamarin.

public ServiceInjectionCallAndReturnViewModel() : this(DependencyService.Get<IConfirmationService>())
{
}

public ServiceInjectionCallAndReturnViewModel(IConfirmationService confirmationService)
{
   _confirmationService = confirmationService;
   ExecuteSomeOperationCommand = new Command(DoSomething);
}

In the Initialize method of the ViewModel we receive the message passed in from code behind and w set initialization to true so that the button will be visible.

Now comes the fun part, we create a ConfirmationService. Let’s start with the interface:

public interface IConfirmationService
{
    Task<bool> AskConfirmation(string message);
}

Pretty straightforward. Now the implementation

public class ConfirmationService : IConfirmationService
{
    public async Task<bool> AskConfirmation(string message)
    {
        var config = new ConfirmConfig()
        {
            Title = "Confirmation",
            Message = message,
            OkText = "Yes",
            CancelText = "No",
        };
        return await UserDialogs.Instance.ConfirmAsync(config);
    }
}

Let’s walk through that. When this program begins the page is created and on appearing it grabs the view model and calls Initialize on it, passing in “My Parameter” (or any parameter you like).

Initialize sets up a property to hold teh parameter, sets _isInitialized to true and calls OnPropertyChanged passing in “Button Is Visible”

The event handler for OnPropertyChanged is in the view model’s base class.

The user is presented with a Button. When she clicks the button the command is fired. This is handled in the view model, using the Dependency Service that was loaded by the ViewModel’s constructor.

Key Points

The take-away from the service injection approach is that virtually none of the work is done in the code-behind. All the work is done in the ViewModel, which delegates to a service the actual work of popping the user dialog.

Unit Tests

It is almost impossible to unit test when the logic is in the code-behind; but when it is in the ViewModel, testing becomes much simpler. In our case, we can use a mock service (which in this case we’ll do with Moq)

[TestMethod]
public void CanConfirmExecutionOfSomeOperation()
{
    // Arrange
    var service = new Mock<IConfirmationService>();
    service.Setup(m => m.AskConfirmation(It.IsAny<string>())).ReturnsAsync(true);

    var vm = new ServiceInjectionCallAndReturnViewModel(service.Object);
    vm.Initialize( "Test" );

    var startingNumberOfOperation = vm.Operations.Count;

    // Act
    vm.ExecuteSomeOperationCommand.Execute(null);

    // Assert
    Assert.AreEqual(startingNumberOfOperation + 1, vm.Operations.Count);
}

We start by creating our mock service, and setting it up so that it will take any string and return true. Next we create our view model, and pass in the (mock) service. We are then ready to run our test. Easy Peasy.

Source

All of the source code for this blog post can be found at https://github.com/XamEsp/XamMessaging

About Rodrigo

Rodrigo Juarez is a full stack and Xamarin Certified Mobile Professional developer working for Belatrix Software. His mission is to solve complex problems for his clients focusing on the results, applying the most adequate technology available and best practices to ensure a cost-effective and high-quality solution. He has 20+ years of experience in a wide variety of projects in the development of applications for web, desktop and mobile using Microsoft technologies in areas such as management, services, insurance, pharmacy and banks.  He can be reached at http://www.rodrigojuarez.com

About Jesse Liberty

Jesse Liberty is the Principal Mobile Developer with IFS Core. He has three decades of experience writing and delivering software projects. He is the author of 2 dozen books and a couple dozen Pluralsight & LinkedIn Learning courses, and has been a Senior Technical Evangelist for Microsoft, a Distinguished Software Engineer for AT&T, a VP for Information Services for Citibank and a Software Architect for PBS. He is a Xamarin Certified Mobile Developer and a Xamarin MVP and a Microsoft MVP.
This entry was posted in Essentials, Xamarin and tagged , , . Bookmark the permalink.