1

I am using Xamarin, C#, on Android platform, and need to popup a Yes/No confirmation dialog when the user clicks an icon from my top action bar. Let's say this icon should trigger a "delete all rows" from a table, and the operation requires user's confirmation.

How should I implement this custom dialog popup and get user's choice? Any working example?

The following code is from my MainActivity : ActionBarActivity, and the following code does not work as expected. No popup dialog is displayed, and the app stops responding after a few seconds.

public override bool OnOptionsItemSelected(IMenuItem item)
{
    var res = OnOptionsItemSelectedAsync(item);

    if (res.Result) return true;
    else return false;
}

public async Task<bool> OnOptionsItemSelectedAsync(IMenuItem item)
{
    var tcs = new TaskCompletionSource<bool>();
    tcs.SetResult(true);

    switch (item.ItemId)
    {
        case Resource.Id.action_delete:
            // Show Yes/No confirmation dialog here, blocking (sync)
            string dialogResponse = await DisplayCustomDialog("Confirm delete", "Are you sure you want to delete all rows?", "YES", "NO");
            //string dialogResponse = DisplayCustomDialog("Confirm delete", "Are you sure you want to delete all rows?", "YES", "NO");
            if ("YES" == dialogResponse)
            {
                //...
                Toast.MakeText(this, "Deleted!", ToastLength.Short).Show();
            }
            break;
        //...
        default: break;
    }
    return tcs.Task.Result;
}

private Task<string> DisplayCustomDialog(string dialogTitle, string dialogMessage, string dialogPositiveBtnLabel, string dialogNegativeBtnLabel)
{
    var tcs = new TaskCompletionSource<string>();

    Android.App.AlertDialog.Builder alert = new Android.App.AlertDialog.Builder(this);
    alert.SetTitle(dialogTitle);
    alert.SetMessage(dialogMessage);
    alert.SetPositiveButton(dialogPositiveBtnLabel, (senderAlert, args) => {
        //Toast.MakeText(this, dialogPositiveBtnLabel + "!", ToastLength.Short).Show();
        tcs.SetResult(dialogPositiveBtnLabel);
    });
    alert.SetNegativeButton(dialogNegativeBtnLabel, (senderAlert, args) => {
        //Toast.MakeText(this, dialogNegativeBtnLabel + "!", ToastLength.Short).Show();
        tcs.SetResult(dialogNegativeBtnLabel);
    });
    Dialog dialog = alert.Create();
    dialog.Show();

    // Test with DisplayAlert()
    //var answer = await DisplayAlert(dialogTitle, dialogMessage, dialogPositiveBtnLabel, dialogNegativeBtnLabel);
    //Debug.WriteLine("Answer: " + answer);

    return tcs.Task;
}
GigelPopescu
  • 31
  • 1
  • 5

1 Answers1

3

You have commented the code which waited for the user to choose an option, so response will be always empty. Change your code this way:

private Task<string> DisplayCustomDialog(string dialogTitle, string dialogMessage, string dialogPositiveBtnLabel, string dialogNegativeBtnLabel)
{
    var tcs = new TaskCompletionSource<string>();

    Android.App.AlertDialog.Builder alert = new Android.App.AlertDialog.Builder(this);
    alert.SetTitle(dialogTitle);
    alert.SetMessage(dialogMessage);
    alert.SetPositiveButton(dialogPositiveBtnLabel, (senderAlert, args) => {
        tcs.SetResult(dialogPositiveBtnLabel);
    });

    alert.SetNegativeButton(dialogNegativeBtnLabel, (senderAlert, args) => {
        tcs.SetResult(dialogNegativeBtnLabel);
    });

    Dialog dialog = alert.Create();
    dialog.Show();

    return tcs.Task;
}

Then, implement the async helper from here to execute a task synchronously:

string dialogResponse = AsyncHelpers.RunSync<string>(() => DisplayCustomDialog("Confirm delete", "Are you sure you want to delete all rows?", "YES", "NO"));

The need to use the helper is because if you Wait() for the task to end that will block de UI thread, as the UI thread is the one responsible on showing the dialog then there will be a deadlock and the app will hang.

Community
  • 1
  • 1
Gusman
  • 14,905
  • 2
  • 34
  • 50
  • Yeah, that async helper you mentioned seems to do the job (I haven't tested it yet), but that is a complicated approach. – GigelPopescu Mar 23 '17 at 22:21
  • That's the only approach if you want to block your code for a response, Alerts on Android are asynchronous, they don't block, they will execute a delegate when the user selects an option, so you need a way to create an asynchronous task and wait for it, there's no simple solution unless you change your code to be fully async, and that would be the best solution. – Gusman Mar 23 '17 at 22:24
  • To solve my use-case (delete table rows after the user clicks an icon on the top action bar AND confirms via a AlertDialog YES/NO popup,I moved the AlertDialog code into my OnOptionsItemSelected(). And on the alert.SetPositiveButton's Click handler I perform the delete operation on the table rows. Seems to work this way, and is pretty simple because I do not have to mess with sync/async. Basically I perform my action in the OnClick handler of the AlertDialog's positive button. Any cons to this approach? – GigelPopescu Mar 23 '17 at 22:29
  • Well, that change you made has completely modified the code you posted, your OnOptionsItemSelected now must not return anything or return always true/false as you aren't waiting for the response. One problem I can think is if you call OnOptionsItemSelected twice before it ends it will execute twice, not sure the behavior it will have calling an alert while another alert is displayed... – Gusman Mar 23 '17 at 22:32
  • Gusman, thanks for the feedback. I've set a semaphore so prevent the situation you described. It should be fine, the action will be performed only if the flag is not set. – GigelPopescu Mar 23 '17 at 22:38
  • I copied this exactly but I don't get a dialog at all. – CompanyDroneFromSector7G Nov 13 '17 at 22:25
  • @CompanyDroneFromSector7G Where are you calling from this code? – Gusman Nov 14 '17 at 10:58