7

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?

Community
  • 1
  • 1
Eugene Smykov
  • 450
  • 2
  • 4
  • 12

1 Answers1

13

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

Community
  • 1
  • 1
Stuart
  • 66,722
  • 7
  • 114
  • 165
  • Ok, I'm trying this stuff :) – Eugene Smykov Jun 11 '13 at 06:58
  • I'm agree that work with UI from VM is not "Feng shui" ;) but WinPhone and iPhone platforms are already launch, and I just needed in Android version of my application. In future, in my new projects, I will necessarily develop according to the canons MVVM :) – Eugene Smykov Jun 11 '13 at 07:02
  • Stuart, a short question about `IMvxAndroidCurrentTopActivity`: Will it always be the same instance, even when the current top activity changes? Background: I am not a fan of service location, instead my classes take explicit dependencies. Would it work if a singleton class of mine has a constructor dependency on `IMvxAndroidCurrentTopActivity` and it could still always operate on the current activity? – Daniel Hilgarth Jul 03 '13 at 22:31
  • @DanielHilgarth Please ask the new question as a new question. I think it'll then be easier to write, easier to read, and easier to reply. Thanks. – Stuart Jul 04 '13 at 08:54
  • @Stuart: Ha, I don't know how many times I posted that exact comment. And now I make the same mistake... ;-) Here is the question: http://stackoverflow.com/questions/17466488/is-imvxandroidcurrenttopactivity-a-singleton Thanks. – Daniel Hilgarth Jul 04 '13 at 09:18