AlertDialogs that Blocks Execution and Wait for input on Andorid (MonoDroid/Xamarin.Android)

This blog post aims at addressing the following:

1. Why is blocking the UI and waiting for the User’s input is sometimes required?
2. How to have an Alert Modal that waits for the user’s input on Android using MonoDroid (Xamarin.Android)

Problem

I have some business logic (controllers), which assume that showing a confirmation box (alertDialog) is a blocking call on the UI. This need not change
while I need to fulfill the requirement and show the screens with a confirm box.

Problem Details

I have seen all the arguments (here and here) and in many other places about how bad is Blocking the UI to wait for a feedback, and I cannot agree more.
However, being good or a bad is one thing and having a requirement for a particular feature (blocking UI alert dialog) is another.

I developed an app on Android using MonoDroid (Xamarin.Android), and it uses MVC design pattern. All the screens are designed to be dummy fragments (classes) that are bound to the Controllers.
The Controllers are the core part of the business for this client. These Controllers were developed on Windows but they only have Business Logic, the requirement for me was to produce similar app in Android (similar to the windows one)
which would use MVC and makes use of these controllers with no changes (or the minimum possible).
This is understandable, considering the fact that these Controllers (including algorithms, libraries, etc) that have been tested, used and trusted.

I developed the app well enough without the need to change any of the controllers and all worked well, UNTIL I need to have a confirmation box.
Windows style of MessageBox or Confirm Box is all synchronous, meaning that it blocks the UI and wait for the user to click Yea/No. This was an assumption that sneaked into these controllers and I need to find a way to comply with that.
Even when I ported the app to iOS, I did not have any issue with this, since iOS uses UI-Blocking mechanism of showing Alert/Confirm dialog boxes (Modal).
However, Android uses Asynch approach to deal with anything that is UI related. This is great news in terms of the user experience, but it is terrible news to me, that I promised to do this app and bring the app as close as possible to Windows and iOS “without changing the controllers”.

I started looking for options, I found Q&A mentioned above on SO and other sites, and they all talk about the same thing were you need to pass a callback to the alertDialog. This is not an option for me because the Controllers assume that we have a blocking call to the UI when we require confirmation from the user.
To Demo this, let’s look at the Logout Button event handler.

	// logout call on Windows/iOS
	public void Logout()
	{
		var args = new ConfirmEventArgs("Are you sure you want to logout?", "Logout?");
		OnConfirmUser(args);
		if (!args.Cancel)
		{
			DataManager.IsUserLoggingOff = true;
			OnApplyWorkflow(new StimulusEventArgs(DataObject as IWorkflowDomain, Constants.Stimuli.Logout));
		}
	}
	

I found (http://stackoverflow.com/questions/2028697/dialogs-alertdialogs-how-to-block-execution-while-dialog-is-up-net-style#10358260 ) this answer on SO, which some people say it works on Android using Java, but this did not work on Xamarin.Android. The reason was that this approach assumed that we send messages at a lower level between threads and then we throw an exception from one and then catch that exception in the other thread.
This did not work on Xamarin.Android, and when asked Xamarin team for help, all they did was showing me the sample code where you need to provide a callback to the messageBox, as if I have not seen it before.

The Solution

After a lots of fiddling around, I ended up coming up with my approach, which basically creates a thread to do the confirmation on, and WAIT for the user’s input.
The reason why we need the new thread is that to avoid blocking the main UI thread while we are waiting for the user input.
The final result was something like this:

	// In the BaseController, I added this method, which checks for platform and creats a new thread on Android only 
	// (since the controllers code is shared cross-platforms)
	protected void RunConfirmAction(Action runnableAction)
	{
		if (runnableAction != null)
		{
			if (Core.Platform.IsAndroid)
			{
				var confirmThread = new Thread(() => runnableAction());
				confirmThread.Start();
			}
			else
			{
				runnableAction();
			}
		}
	}
	
	// The call to the logout method has now changed like this:
	RunConfirmAction(Logout);
	
	// the implemtation of the MessageBox waiting is like this:
	public DialogResult MessageBoxShow(string message, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton)
	{
		if (_CurrentContext != null && _CurrentContext.Screen != null && MainForm.MainActivity != null)
		{
			Action<bool> callback = OnConfirmCallBack;
			_IsCurrentlyInConfirmProcess = true;
			Action messageBoxDelegate = () => MessageBox.Show(((Activity)MainForm.MainActivity), callback, message, caption, buttons);
			RunOnMainUiThread(messageBoxDelegate);
			while (_IsCurrentlyInConfirmProcess)
			{
				Thread.Sleep(1000);
			}				
		}
		else
		{
			LogHandler.LogError("Trying to display a Message box with no activity in the CurrentContext. Message was: " + message);
		}
		return _ConfirmBoxResult ? DialogResult.OK : DialogResult.No;

	}

	private void OnConfirmCallBack(bool confirmResult)
	{
		_ConfirmBoxResult = confirmResult;
		_IsCurrentlyInConfirmProcess = false;
	}

	private bool _ConfirmBoxResult = false;
	private bool _IsCurrentlyInConfirmProcess = false;
	

The code for the MessageBox class can be found here, I have created this long time ago and it works nicely for green-field projects where you can post callbacks to the confirmation box.

And that is all, after all this fiddling, I got something that satisfy the requirements, and my client was very happy that they do not need to change their Controllers (which means, less bugs, less testing, quicker delivery).
Thus, I hope I have demo-ed good grounds for why it is sometimes necessary to have a blocking UI alertDialog on Android, and how it can be done.
If you have any questions, or you think this could be done nicer, better or simpler, I would love to hear from you.

6 Replies to “AlertDialogs that Blocks Execution and Wait for input on Andorid (MonoDroid/Xamarin.Android)”

  1. Thanks for this interesting article, I’ve been searching for a modal dialog for Android as well. I will give it a shot sometime later.
    Are you using MvvmCross as well? The code for Signature seems to be Mvx compatible; I came across your website while searching for a free Signature component.

    1. Thanks Khalil, I am not using Mvvm, we are using MVC pattern with our own workflow and routing engines. However, the SignatureView should work regardless, the way you are presenting and binding it could change. Good luck -:)

  2. Hi! Thanks for that, I’m trying to adapt it to my Windows.Forms.MessageBox (that is ok on iOS and ASP.NET). Do you have the link for the complete code?

    “The code for the MessageBox class can be found here” but “here” do not have a link.

    Thanks a lot.

    1. Hey, thanks for the comment, I have just update the blog post to show you where u could find the full code. It was an answer that I posted long ago on StackOverflow 🙂
      Hope this helps

  3. Hi Has,

    try to utilize the class System.Threading.Task. That way, you dont have to deal with polling for the result.

    public ViewResult ShowMsgBox(string text, string caption, MsgBoxButtons buttons, MsgBoxIcon icon, MsgBoxDefaultButton defaultButton, string details)
    {
    var task = ShowMsgBoxTask((Activity)CurrentContext, text, caption, buttons, icon, defaultButton, details);
    Task.WaitAll(task);
    return task.Result;
    }

    [NotNull]
    private static Task ShowMsgBoxTask([NotNull] Activity activity, [CanBeNull]string text, [CanBeNull]string caption, MsgBoxButtons buttons, MsgBoxIcon icon, MsgBoxDefaultButton defaultButton, [CanBeNull]string details)
    {
    if (activity == null)
    throw new ArgumentNullException(“activity”);
    text = text ?? “”;
    caption = caption ?? “”;

    var tcs = new TaskCompletionSource();

    AlertDialog.Builder ab = new AlertDialog.Builder(activity);
    if (string.IsNullOrEmpty(details))
    {
    if (!string.IsNullOrEmpty(text))
    text += “rnrn”;
    text += details;
    }
    ab = ab.SetTitle(caption).SetMessage(text);
    switch (icon)
    {
    case MsgBoxIcon.Asterisk:
    ab.SetIcon(Android.Resource.Drawable.IcDialogInfo);
    break;
    case MsgBoxIcon.Exclamation:
    ab.SetIcon(Android.Resource.Drawable.IcDialogAlert);
    break;
    case MsgBoxIcon.Hand:
    ab.SetIcon(Android.Resource.Drawable.StatNotifyError);
    break;
    case MsgBoxIcon.Question:
    ab.SetIcon(Android.Resource.Drawable.IcMenuHelp);
    break;
    }

    EventHandler ok = (sender, args) => tcs.SetResult(ViewResult.OK);
    EventHandler cancel = (sender, args) => tcs.SetResult(ViewResult.Cancel);
    EventHandler yes = (sender, args) => tcs.SetResult(ViewResult.Yes);
    EventHandler no = (sender, args) => tcs.SetResult(ViewResult.No);
    EventHandler retry = (sender, args) => tcs.SetResult(ViewResult.Retry);
    EventHandler abort = (sender, args) => tcs.SetResult(ViewResult.Abort);
    EventHandler ignore = (sender, args) => tcs.SetResult(ViewResult.Ignore);

    switch (buttons)
    {
    case MsgBoxButtons.OK:
    ab = ab.SetPositiveButton(“OK”, ok);
    break;
    case MsgBoxButtons.OKCancel:
    ab = defaultButton == MsgBoxDefaultButton.Button1
    ? ab.SetPositiveButton(“OK”, ok)
    : ab.SetNegativeButton(“OK”, ok);
    ab = defaultButton == MsgBoxDefaultButton.Button2
    ? ab.SetPositiveButton(“Cancel”, cancel)
    : ab.SetNegativeButton(“Cancel”, cancel);
    break;
    case MsgBoxButtons.RetryCancel:
    ab = defaultButton == MsgBoxDefaultButton.Button1
    ? ab.SetPositiveButton(“Retry”, retry)
    : ab.SetNegativeButton(“Retry”, retry);
    ab = defaultButton == MsgBoxDefaultButton.Button2
    ? ab.SetPositiveButton(“Cancel”, cancel)
    : ab.SetNegativeButton(“Cancel”, cancel);
    break;
    case MsgBoxButtons.YesNo:
    ab = defaultButton == MsgBoxDefaultButton.Button1
    ? ab.SetPositiveButton(“Yes”, yes)
    : ab.SetNegativeButton(“Yes”, yes);
    ab = defaultButton == MsgBoxDefaultButton.Button2
    ? ab.SetPositiveButton(“No”, no)
    : ab.SetNegativeButton(“No”, no);
    break;
    case MsgBoxButtons.YesNoCancel:
    ab = defaultButton == MsgBoxDefaultButton.Button1
    ? ab.SetPositiveButton(“Yes”, yes)
    : ab.SetNegativeButton(“Yes”, yes);
    ab = defaultButton == MsgBoxDefaultButton.Button2
    ? ab.SetPositiveButton(“No”, no)
    : ab.SetNegativeButton(“No”, no);
    ab = defaultButton == MsgBoxDefaultButton.Button3
    ? ab.SetPositiveButton(“Cancel”, cancel)
    : ab.SetNegativeButton(“Cancel”, cancel);
    break;
    case MsgBoxButtons.AbortRetryIgnore:
    ab = defaultButton == MsgBoxDefaultButton.Button1
    ? ab.SetPositiveButton(“Abort”, abort)
    : ab.SetNegativeButton(“Abort”, abort);
    ab = defaultButton == MsgBoxDefaultButton.Button2
    ? ab.SetPositiveButton(“Retry”, retry)
    : ab.SetNegativeButton(“Retry”, retry);
    ab = defaultButton == MsgBoxDefaultButton.Button3
    ? ab.SetPositiveButton(“Ignore”, ignore)
    : ab.SetNegativeButton(“Ignore”, ignore);
    break;
    }

    activity.RunOnUiThread(() => ab.Show());
    return tcs.Task;
    }

    public enum ViewResult
    {
    None = 0,
    OK = 1,
    Cancel = 2,
    Abort = 3,
    Retry = 4,
    Ignore = 5,
    Yes = 6,
    No = 7,
    }

    public enum MsgBoxButtons
    {
    OK = 0,
    OKCancel = 1,
    AbortRetryIgnore = 2,
    YesNoCancel = 3,
    YesNo = 4,
    RetryCancel = 5,
    }
    public enum MsgBoxDefaultButton
    {
    Button1 = 0,
    Button2 = 256,
    Button3 = 512,
    }
    public enum MsgBoxIcon
    {
    None = 0,
    Hand = 16,
    Question = 32,
    Exclamation = 48,
    Asterisk = 64,
    }

    1. Thanks Redwolf, You are absolutely right. I wrote this blog post a while back when I was not familiar with System.Threading.Task 🙂 Will update accordingly… Thanks again for the hint

Leave a Reply

Your email address will not be published. Required fields are marked *