Show AlertDialog from ViewModel using MvvmCross

Eugene Smykov picture Eugene Smykov · Jun 11, 2013 · Viewed 9.3k times · Source

I am using MvvmCross for creation my Android-app and I faced with the following problem:

When I'm trying to show AlertDialog, that was created in ViewModel, the

"Unhandled Exception: Android.Views.WindowManagerBadTokenException" appears.

public class MyViewModel : MvxViewModel
{
    public ICommand ShowAlertCommand { get; private set; }

    public AuthorizationViewModel()
    {
        ShowAlertCommand = new MvxCommand(() =>
            {
                var adb = new AlertDialog.Builder(Application.Context); 
                adb.SetTitle("Title here");
                adb.SetMessage("Message here");  
                adb.SetIcon(Resource.Drawable.Icon);
                adb.SetPositiveButton("OK", (sender, args) => { /* some logic */});   
                adb.SetNegativeButton("Cancel", (sender, args) => { /* close alertDialog */});

                adb.Create().Show();
            });
    }
}

When I was researching I have found that it happens because of transmission of the reference to the Context but not on the Activity in the AlertDialog.Builder.

In this topic I found the following decision: Receive references to the current Activity through the use of GetService(), but I didn't found mvvmcross plugins for work with IMvxServiceConsumer, IMvxAndroidCurrentTopActivity interfaces.

My question is can I show AlertDialog from ViewModel? And how can I get the reference to Activity, but not to the Application.Context? And what is the correct way to close AlertDialog that the user would stay on the current View?

Answer

Stuart picture Stuart · Jun 11, 2013

In general, you should try not to put this type of code into ViewModels

  • because ViewModels should stay platform independent
  • because ViewModels should be unit testable - and it's hard to unit test when the code shows a dialog

I'd also recommend you don't put code like this inside a ViewModel Constructor - these constructors are generally called during navigations and displaying a Dialog during a transition is likely to be problematic.


With those things said, if you do want to get hold of the current top Activity within any code, then you can do this using the IMvxAndroidCurrentTopActivity

public interface IMvxAndroidCurrentTopActivity
{
    Activity Activity { get; }
}

Using this, any code can get the current Activity using:

var top = Mvx.Resolve<IMvxAndroidCurrentTopActivity>();
var act = top.Activity;
if (act == null)
{
   // this can happen during transitions
   // - you need to be sure that this won't happen for your code
   throw new MvxException("Cannot get current top activity");
}

var dlg = new AlertDialog.Builder(act); 
//...
dlg.Create().Show();

The use of IMvxAndroidCurrentTopActivity is discussed in MvvmCross: How to pass Android context down to MvxCommand?

The approach taken in that question/answer is also one of the ways I would generally approach showing dialogs from a ViewModel:

  • I would create an IFooDialog interface
  • Ideally I would probably make this interface asynchronous - e.g. using async or using an Action<DialogResult> callback parameter
  • on each platform I would implement that in the UI project
  • the ViewModels can then use IFooDialog when a dialog is needed and each platform can respond with an appropriate UI action

This 'Dialog Service' type of approach is common in Mvvm - e.g. see articles like http://www.codeproject.com/Articles/36745/Showing-Dialogs-When-Using-the-MVVM-Pattern (although that article is very Windows specific!)

There are also a few other questions on here about MvvmCross and dialogs - although they may contain reference to older v1 or vNext code - e.g. Alerts or Popups in MvvmCross and Unable run ProgressDialog - BadTokenException while showind