Wednesday, September 18, 2013

A first implementation of alert dialog support in Xamarin + MVVMCross


Update: You can use this MvvmCross plugin: https://github.com/brianchance/MvvmCross-UserInteraction
              At this moment however, the plugin doesn't have an implementation for Windows Phone and Windows Store, it's only for Android and iOS.

The implementation steps:
#1. In the Core PCL project, have the dialog interface declared in IDialogService.cs.
namespace MyApp.Core.Services
{
    public interface IDialogService
    {
        Task<bool?> ShowAsync(string message, string title, string OKButtonContent, string CancelButtonContent);
    }
}

#2. In the platform specific project, implement the dialog support. Example for Android:

namespace MyApp.Droid.Services
{
    public class DialogService : IDialogService
    {
        public Task<bool?> ShowAsync(string message, string title, string OKButtonContent, string CancelButtonContent)
        {
            var tcs = new TaskCompletionSource<bool?>();

            var mvxTopActivity = Mvx.Resolve<IMvxAndroidCurrentTopActivity>();
            AlertDialog.Builder builder = new AlertDialog.Builder(mvxTopActivity.Activity);
            builder.SetTitle(title)
                   .SetMessage(message)
                   .SetCancelable(false)
                   .SetPositiveButton(OKButtonContent, (s, args) =>
                    {
                        tcs.SetResult(true);
                    })
                   .SetNegativeButton(CancelButtonContent, (s, args) =>
                   {
                       tcs.SetResult(false);
                   });

            builder.Create().Show();
            return tcs.Task;
        }
    }
}


#3.
Still in the platform specific project, register the service:

namespace MyApp.Droid
{
    public class Setup : MvxAndroidSetup
    {
        public Setup(Context applicationContext) : base(applicationContext)
        {
        }

        protected override void InitializeLastChance()
        {
            Mvx.RegisterSingleton<IDialogService>(new DialogService());
            base.InitializeLastChance();
        }
    }
}

#4. In the view models, here is how I can call the display of the dialog:


void async DeleteUser()
{
    var result = await Mvx.Resolve<IDialogService>().Show("Are you sure you want to delete selected user?", "Confirmation", "OK", "Cancel");
    if(result == true)
    {
        // delete user...
    }
}


This is a first implementation of showing dialogs. There’s a lot of possible parameterization / customization, but it needs to take into account the capabilities and behavior of all platforms.
In tests, the IDialogService implementation obviously does not call showing a dialog, it does nothing.

One interesting discussion I had with Greg Shackles is about using a different approach than what I showed here: have the view full responsibility of displaying the dialog and communicate with view model using commands \ methods, etc. I understand the idea but I don't have a very clear way of doing this so until I see all the issues I think I will use this solution.

10 comments:

Unknown said...

Andrei -
I have a similar service here https://github.com/brianchance/MvvmCross-UserInteraction

It passes action methods as parameters, but I like your idea of returning Task's. I might add additional methods (AlertAsync, ConfirmAsync) etc.

Brian

Andrei Nitescu said...

Thanks Brian for sharing the link, I didn't know such plugin exists.

Maciej Kłusowski said...

Great work Andrei,
@Brian does your pligin work with PCL?

Maciej Kłusowski said...

Great code, thanks.
@Brian does your plugin work with PCL?

Unknown said...

@maciej - the PCL portion is just the interface definition. Dialogs are platform specific.

Unknown said...

@maciej - The PCL library includes the interface definition. Dialogs are platform specific.

Andrei Nitescu said...
This comment has been removed by the author.
Filip said...

I can't make this work. Despite some errors in sample ( var result = await Mvx.Resolve().Show instead of var result = await Mvx.Resolve().ShowAsync, void async instead of async void) I keep getting same error: Java.Lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare(). Did I skip something? I basicaly just copied example. (I'm using ActiveBarSherlock with MvvmCross View to triger the command in ViewModel wich calls ShowAsync )

Filip said...

Never mind about my prior comment. VS didn't deploy my new code so I was running in circles. Tnx.

Anonymous said...

Hello,

thanks a lot for this article !
I'm a beginner with xamarin android AND MVVM Cross and for me it's enough difficult to learn mobile dev with the concept of MVVM Cross ...

Your article help me a lot ...

Thanks and good contination