Dialogs / AlertDialogs: How to "block execution" while dialog is up (.NET-style)

Asked 12 years, 4 months ago
Viewed 105k times
 
72

Coming from the .NET-environment, I'm now looking to understand how Dialogs work in Android.

In .NET, when calling MessageBox.Show(...) that creates and shows a popup dialog. In the call to Show I can specify what buttons should be available in the popup, for example:

DialogResult myDialogResult = MessageBox.Show("My text here", "My caption here", MessageBoxButtons.YesNoCancel);

As you can see, the call to Show returns a DialogResult when a button is pressed in the popup, informing me what button was clicked. Note that in .NET, execution is halted at the line where the call to Show(...) is made, so it can return the value when a button is pressed.

If I in the above example press "No" the myDialogResult will be equal to

myDialogResult == DialogResult.No

Since I find the .NET-way of using/creating popups very easy and intuitive, I would like that way of creating popups in Android too.

So, the question is if anyone know how to "halt execution" like with the MessageBox.Show, and then return a value whenever the Button is pressed (and the dialog goes away)?

Edit 1

To be a little bit more clear:

I need for the execution to halt and wait until the user has chosen a button to click in the popup. The code that follow the call to show the Dialog is dependent on what button is clicked in the Dialog.

That's why I cannot use what Erich and Alex suggest, since writing code in the onClick-methods as suggested below is not going to work. The reason is that I cannot continue the "normal execution". Let me take an example:

Let me take an example:

int nextStep = 0; // this variable will not be reached from within the onClick-methods

AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage("Hello!")
       .setPositiveButton("Ok", new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int id) {
                nextStep = 1; // *** COMPILER ERROR!! ***
            }
        })
        .setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int id) {
                nextStep = 2; // *** COMPILER ERROR!! ***
            }
        })
        .create().show();

if (nextStep == 1)
{
    // then do some damage
}
else if (nextStep == 2
    // dont do damage

If I wanted the execution to be dependent on the choice in the popup, I would somehow have to make all the variables in the "normal execution" (in this case nextStep) available in the onClick-methods, and that sounds like hell to me.

Edit 2

Another obvious example would be a popup asking "Do you want to continue" with the options "Yes" and "No".

If the user presses "Yes", the whole method should be aborted otherwise it should continue execution. How do you solve that nicely?

 
  •  
    see if fupsduck's added paragraph helps   Jan 8, 2010 at 16:49
  • 4
     
     
    As an alternative, you could also design your "dialog" as an Activity that has the same look-and-feel as a dialog. If your dialog has a lot of logic, it might be easier to implement this way.   Jan 8, 2010 at 17:00
  • 2
     
     
    Regarding the alert dialog, .NET is great, and Android sucks. In a few cases I need MessageBox to help debugging. In .NET, the code after the MessageBox won't run until you close the MessageBox. However, in Android, it does not work. I think in Windows, there is another message loop when showing a modal dialog. In Android, I think the window and the dialog uses the same message loop. 
    – Zach
     Aug 3, 2014 at 3:53
  •  
    Instead of having If (nextStep == 1) { } and If (nextStep == 2) { }, you need to have a void called, say, public void nextStep1 and public void nextStep2 that get called when the user clicks a certain button. If they share code afterwords, you can make another void that they each call afterwords with the rest of your code in it. 
    – Cullub
     Oct 16, 2014 at 12:54 
  •  
    I think the Android guy @Romain Guy just said that "you should not design a program that look nice and clean to make a choice and act according to the choice". If there is a sequence of similar choices and related each other, it might need a 100 times of code lines in android. I don't known what is the difficulty or side affect to add this to Android library, but I think maybe it is difficult from them. 
    – rml
     Apr 23, 2015 at 18:16

20 Answers

0

you set onclick listeners to you buttons. dissmis dialog and do your action. Don't need to halt anything

protected Dialog onCreateDialog(int id) {
  return new AlertDialog.Builder(this).setTitle(R.string.no_connection).setIcon(android.R.drawable.ic_dialog_alert).setView(textEntryView).setPositiveButton(R.string.exit, new DialogInterface.OnClickListener() 
{

   public void onClick(DialogInterface dialog, int whichButton) {

  // Here goes what you want to do
  }

})
}

to call - ex - showDialog(DIALOG_ERROR_PREF);

More http://developer.android.com/guide/topics/ui/dialogs.html

 
  •  
    Hey, that was not really what I wanted I think. Whenever a popup is display, the code that follows should NOT be executed until after I have pressed one of the buttons in the popup. The value of the button-press should be returned to the caller, so that the caller then can decide what to do... I will update my question above. 
    – Ted
     Jan 8, 2010 at 16:07
  • 2
     
     
    Since it's not really a .Net i think you have to adapt. And it's not a desktop either. Break your flow into events. You've did some action to display dialog. Now User reacts by pressing the buttons. Well - call doDamage(); dontDoDamage() methods. And continue from there   Jan 8, 2010 at 16:37 
 
22

In Android, the structure is different from .NET:

AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage("Hello!")
       .setPositiveButton("Ok", new DialogInterface.OnClickListener() {
           public void onClick(DialogInterface dialog, int id) {
               // Handle Ok
           }
       })
       .setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
           public void onClick(DialogInterface dialog, int id) {
               // Handle Cancel
           }
       })
       .create();

Will get you a dialog with two buttons and you handle the button clicks with callbacks. You might be able to write some code to make the syntax more closely resemble .NET, but the dialog lifecycle is pretty intertwined with Activity, so in the end, it might be more trouble than it's worth. Additional dialog references are here.

 
-1

This is the simplest way:

new AlertDialog.Builder(this).setTitle("title").setMessage("message").create().show();
 
  •  
    Yes, but that doesnt solve the problem Im addressing I think, see my "EDIT 1" aboive in my question =) regards 
    – Ted
     Jan 8, 2010 at 16:24
 
5

In an attempt to optimize memory and performance dialogs in Android are asynchronous (they are also managed for this reason). Comming from the Windows world, you are used to modal dialogs. Android dialogs are modal but more like non-modal when it comes to execution. Execution does not stop after displaying a dialog.

The best description of Dialogs in Android I have seen is in "Pro Android" http://www.apress.com/book/view/1430215968

This is not a perfect explanation but it should help you to wrap your brain around the differences between Dialogs in Windows and Android. In Windows you want to do A, ask a question with a dialog, and then do B or C. In android design A with all the code you need for B and C in the onClick() of the OnClickListener(s) for the dialog. Then do A and launch the dialog. You’re done with A! When the user clicks a button B or C will get executed.

Windows
-------
A code
launch dialog
user picks B or C
B or C code
done!

Android
-------
OnClick for B code (does not get executed yet)
OnClick for C code (does not get executed yet)
A code
launch dialog
done!
user picks B or C
 
  • 1
     
     
    If there is nothing else in this thread, then this is your case. But you always have something else there. So you have to prevent the other things from happening or to handle dozens or millions of cases that may change the status and makes B or C functions do not work correctly. The Android way makes it too much complicated. 
    – rml
     Sep 27, 2013 at 16:53
  •  
    It's not really any more complicated, it's just different. 
    – Cullub
     Oct 16, 2014 at 12:49
  • 2
     
     
    If there is a lots similar questions to be answered in sequence, and each choice will affect the later process, then, it will be a big mess use non stop dialog used in android, but it will be much much more easier using windows dialog. ABSOLUTELY NOT JUST different. 
    – rml
     Apr 23, 2015 at 17:56
49
 

Ted, you don't want to do this, really :) The biggest reason is that if you block the UI thread while you are displaying a Dialog, you will block the thread that's in charge of drawing and handling the events of your Dialog. Which means your dialog will be unresponsive. You will also cause ANRs if the user takes more than a few seconds to click the dialog.

Erich's answer is exactly what you need. I know it's not what you want, but that doesn't matter. We've designed Android to prevent developers from writing synchronous dialogs so you don't really have much of a choice.

 
  • 20
     
     
    It's a mess because you are architecting your code in a way that's not meant to be with Android. Just do it differently. If you refuse to do so, it's your problem. For instance, instead of using 3 blocking dialogs (which I find to be a terrible and lazy UI by the way), why don't you have an Activity will all the questions and only when the user validates you start doing whatever your app is supposed to be doing. You asked a question, we've answered and so far the only thing you've been saying is that you don't like our answers. Sorry, but they won't change.   Jan 9, 2010 at 19:36
  • 64
     
     
    Stop telling people that they shouldn't try to do what they want to do. If anything's "terrible and lazy", that (answer) would be it. Of course you can make modal dialogs in Android, you just need to write more code. There's nothing bad about that UI, from a user's perspective. Lots of UI frameworks, not just Windows, support this concept. If there was a simple one-line answer, the poster probably wouldn't have needed to ask the question. If you don't want to do the work of answering his question, just leave it alone. 
    – Nate
     Aug 12, 2011 at 9:38 
  • 6
     
     
    @radhoo Because of small details like this, is why I see so much horrendous mobile apps (iOS apps that look designed by a Java guy and Android aberrations designed by C++ people) (for example). You have to learn and understand the platform you're working in, not necessarily force your way though it with your own ideas. Each platform has its strengths and weaknesses, like it or not. The lifecycle model in Android is designed that way, deal with it and learn to architecture around it.   Apr 15, 2013 at 21:46
  • 8
     
     
    @rml I work on the Android framework team and we've specifically designed the framework to not have modal dialogs that block the current execution thread. I know how it could be done but it's not the model we chose for Android.   Sep 27, 2013 at 18:41
  • 38
     
     
    Please note that the OP certainly didn't want to actually stop the UI thread, just wanted to block the user workflow. Since that's a valid and common use case (as users hardly ever perform multiple parallel interactions with their gadgets, and systems commonly need to wait for them), it's natural to expect some trivial modal dialog API seamlessly matching the "single-threaded user" reality. It's understandable if it's not easy to do nicely. But if the Android team could come up with a smart way to make it as trivial as illustrated in the question, it could only help us all. Thx! 
    – Sz.
     Jan 12, 2014 at 13:12
11

In Android Dialogs are asynchronous so you're going to have to structure your code a little differently.

So in C# your logic ran something like this in pseudocode:

void doSomeStuff() {
    int result = showDialog("Pick Yes or No");

    if (result == YES) {
        //do stuff for yes
    }
    else if (result == NO) {
        //do stuff for no
    }

    //finish off here
}

For Android it's going to have to be less neat. Think of it like so. You'll have an OnClickListener like this:

public void onClick(DialogInterface dialog, int whichButton) {
   if (whichButton == BUTTON_POSITIVE) {
      doOptionYes();
   }
   else if (whichButton == BUTTON_NEGATIVE) {
      doOptionNo();
   }
}

Which is then supported by the following methods:

void doOptionYes() {
    //do stuff for yes
    endThings();
}

void doOptionNo() {
    //do stuff for no
    endThings();
}

void endThings() {
    //clean up here
}

So what was one method is now four. It may not seem as neat but that's how it works I'm afraid.

 
  • 1
     
     
    Thx Dave! Yes, that was what I have learned from all these answers. Unfortunately I have to say... Thx again =) 
    – Ted
     Jan 8, 2010 at 20:42
  • 3
     
     
    i find this a bit frustrating because sometimes my flow of execution bypasses the dialog if everything ok. if there is a problem i ask "keep going" or "forget it". so now i have 2 ways of getting to the keep going code. so i have to put that in its own method. so i have to redo all the setup that got to the dialog all over again. bad design IMHO. 
    – steveh
     Feb 11, 2013 at 8:46
0

just to answer your question...btw sry that i'm 9 months late:D...there's a "workaround" 4 this kind of problems. i.e.

new AlertDialog.Builder(some_class.this).setTitle("bla").setMessage("bla bla").show();
wait();

simply add wait();

and them in the OnClickListener start the class again with notify() something like this

@Override
    public void onClick(DialogInterface dialog, int item) {
        Toast.makeText(getApplicationContext(), "test", Toast.LENGTH_LONG).show();
        **notify**();
        dialog.cancel();
    }

the same workaround goes 4 toasts and other async calls in android

 
  •  
    Thanks =) If I ever start looking at Android again, I will try it. I have however dropped Android. At least for now. 
    – Ted
     Nov 22, 2010 at 20:28
  •  
    hehe i said i'm sorry:P just had the same problem and wanted people to know the workaround...of course it not simple as this...i ended up needing a new thread and 2 new runnables that where synchronized and so on...a little devilish problem if someone needs the full code...ask:) its big, i mean the code um oh hm 
    – mc_fish
     Nov 25, 2010 at 18:45 
  •  
    Hehe, thx for the info =) I'll remember that If I get into Android developement again =) 
    – Ted
     Nov 29, 2010 at 17:24
  • 2
     
     
    If you use this "workaround" you will get an ANR error in your app if your user doesn't reply with 10 seconds. This won't work.   Dec 29, 2011 at 16:49
4

Ted, as you probably found out, you unfortunately can't do that on Android. Dialogs are modal, but asynchronous, and it will definitely disrupt the sequence you're trying to establish as you would have done on .NET (or Windows for that matter). You will have to twist your code around and break some logic that would have been very easy to follow based on your example.

Another very simple example is to save data in a file, only to find out that the file is already there and asking to overwrite it or not. Instead of displaying a dialog and having an if statement to act upon the result (Yes/No), you will have to use callbacks (called listeners in Java) and split your logic into several functions.

On Windows, when a dialog is shown, the message pump continues in the background (only the current message being processed is on hold), and that works just fine. That allows users to move your app and let is repaint while you're displaying a dialog box for example. WinMo supports synchronous modal dialogs, so does BlackBerry but just not Android.

 
  • 16
     
     
    Microsoft implemented 2 decades ago modal dialog boxes without any blocking that makes the code very fluid and easy to read (any Windows developer can attest that), by using a second message pump to handle the dialog box interaction while dispatching non-dialog box messages to the app, which remains unblocked while the dialog box is shown. Android has no such implementation so you are required to split your code. Android designers prefer to tell us "this is our way or the highway", instead of listening the what their customers (developers) need   Apr 13, 2011 at 14:59
0

Try this in a Thread (not the UI-Thread):

final CountDownLatch latch = new CountDownLatch(1);
handler.post(new Runnable() {
  @Override
  public void run() {
    OnClickListener okListener = new OnClickListener() {
    @Override
    public void onClick(DialogInterface dialog, int which) {
      dialog.dismiss();
      latch.countDown();
    }
  };

  AlertDialog dialog = new AlertDialog.Builder(context).setTitle(title)
    .setMessage(msg).setPositiveButton("OK", okListener).create();
  dialog.show();
}
});
try {
  latch.await();
} catch (InterruptedException e) {
  e.printStackTrace();
}
 
8
PasswordDialog dlg = new PasswordDialog(this);

if(dlg.showDialog() == DialogResult.OK)

{

    //blabla, anything your self

}


public class PasswordDialog extends Dialog
{
    int dialogResult;
    Handler mHandler ;

    public PasswordDialog(Activity context, String mailName, boolean retry)
    {

        super(context);
        setOwnerActivity(context);
        onCreate();
        TextView promptLbl = (TextView) findViewById(R.id.promptLbl);
        promptLbl.setText("Input password/n" + mailName);
    }
    public int getDialogResult()
    {
        return dialogResult;
    }
    public void setDialogResult(int dialogResult)
    {
        this.dialogResult = dialogResult;
    }
    /** Called when the activity is first created. */

    public void onCreate() {
        setContentView(R.layout.password_dialog);
        findViewById(R.id.cancelBtn).setOnClickListener(new android.view.View.OnClickListener() {

            @Override
            public void onClick(View paramView)
            {
                endDialog(DialogResult.CANCEL);
            }
            });
        findViewById(R.id.okBtn).setOnClickListener(new android.view.View.OnClickListener() {

            @Override
            public void onClick(View paramView)
            {
                endDialog(DialogResult.OK);
            }
            });
        }

    public void endDialog(int result)
    {
        dismiss();
        setDialogResult(result);
        Message m = mHandler.obtainMessage();
        mHandler.sendMessage(m);
    }

    public int showDialog()
    {
        mHandler = new Handler() {
            @Override
              public void handleMessage(Message mesg) {
                  // process incoming messages here
                //super.handleMessage(msg);
                throw new RuntimeException();
              }
          };
        super.show();
        try {
            Looper.getMainLooper().loop();
        }
        catch(RuntimeException e2)
        {
        }
        return dialogResult;
    }

}
 
2

The cleanest and simplest solution is to use your own listener interface so that when the user clicks on the ok button your listener is called with the return value. This method does nothing fancy or complicated and respects android principles.

Define your listener interface as follows:

public interface EditListener
/* Used to get an integer return value from a dialog
 * 
 */
{
 void returnValue(int value); 
}

For my application I created an EditValue class which uses AlertDialog and which I call whenever I want to edit an integer value. Note how the EditListener interface is passed as an argument to this code. When the user clicks on the OK button the value will be returned to your calling code via the EditListener method:

public final class EditValue
/* Used to edit a value using an alert dialog
 * The edited value is returned via the returnValue method of the supplied EditListener             interface
 * Could be modified with different constructors to edit double/float etc
 */
{
 public EditValue(final Activity parent, int value, String title, String message,
                  final EditListener editListener)
 {AlertDialog.Builder alert= new AlertDialog.Builder(parent);
  if(title==null) title= message;
  else if(message==null) message= title;
  if(title!=null) alert.setTitle(title);
  if(message!=null) alert.setMessage(message);

  // Set an EditText view to get user input 
  final EditText input = new EditText(parent);
  input.setText(String.valueOf(value));
  input.setInputType(InputType.TYPE_CLASS_NUMBER);
  alert.setView(input);

  alert.setPositiveButton("OK",new DialogInterface.OnClickListener()
  {public void onClick(DialogInterface dialog, int which)
   {try
    {int newValue= Integer.valueOf(input.getText().toString());
     editListener.returnValue(newValue);
     dialog.dismiss();
    }catch(NumberFormatException err) { }
   }
  });

  alert.setNegativeButton("Cancel", new DialogInterface.OnClickListener()
  {public void onClick(DialogInterface dialog, int which)
   {dialog.dismiss();
    }
  });

  alert.show();
 }
}

Finally when you use EditValue you need to declare your EditListener and you can now access the return value and do what you want to do:

 new EditValue(main,AnchorManager.anchorageLimit,
               main.res.getString(R.string.config_anchorage_limit),null,
               new EditListener()
 {public void returnValue(int value) {AnchorManager.anchorageLimit= value;}
  }
 );
 
1

Use android.app.Dialog.setOnDismissListener(OnDismissListener listener).

 
20

A simplified version of Daniel's answer above. This function gets a yes or no from user in an alert dialog but could easily be modified to get other input.

private boolean mResult;
public boolean getYesNoWithExecutionStop(String title, String message, Context context) {
    // make a handler that throws a runtime exception when a message is received
    final Handler handler = new Handler() {
        @Override
        public void handleMessage(Message mesg) {
            throw new RuntimeException();
        } 
    };

    // make a text input dialog and show it
    AlertDialog.Builder alert = new AlertDialog.Builder(context);
    alert.setTitle(title);
    alert.setMessage(message);
    alert.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int whichButton) {
            mResult = true;
            handler.sendMessage(handler.obtainMessage());
        }
    });
    alert.setNegativeButton("No", new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int whichButton) {
            mResult = false;
            handler.sendMessage(handler.obtainMessage());
        }
    });
    alert.show();

    // loop till a runtime exception is triggered.
    try { Looper.loop(); }
    catch(RuntimeException e2) {}

    return mResult;
}
 
  •  
    Thanks, this one works fine. I prefer Erich's way but it's not blocking execution (the solution I often use). At least your way works as intended by the question and the answer is interesting.   Oct 22, 2012 at 13:13 
  •  
    This is the most related answer to the question and it is a creative solution But it fails sometime with a Fatal error like this: A/libc﹕ Fatal signal 11 (SIGSEGV), code 1, fault addr 0x1 in tid 274... Is anyone get a solution about this? 
    – rml
     Apr 23, 2015 at 17:57
  •  
    I'd like to say, that this also works for HTTP requests. I have just done a HTTP request via Volley, which has this idea that the code must continue (in the main threat) while doing the HTTP request. Great service, however, why not let us decide as programmers, whether or not we want to sit and make gymnastics with either an endless call-depth-level, or make our own threading if so? Your excellent example above worked without a single screw or nut added. And now I have modal HTTP calls.. I sorted out the AlertDialog via normal threading, though (!). Where can one share with the rest of you   Oct 15, 2020 at 12:13
1

I am new to the Android/Java world and was surprised to find out here (unless I don't understand what I read) that modal dialogs don't work. For some very obscure reasons for me at the moment, I got this "ShowMessage" equivalent with an ok button that works on my tablet in a very modal manner.

From my TDialogs.java module:

class DialogMes 
{

  AlertDialog alertDialog ;
  private final Message NO_HANDLER = null;
  public DialogMes(Activity parent,String aTitle, String mes)
  {
    alertDialog = new AlertDialog.Builder(parent).create();
    alertDialog.setTitle(aTitle);    
    alertDialog.setMessage(mes) ;  
    alertDialog.setButton("OK",NO_HANDLER) ;    
    alertDialog.show() ; 
  } 
}

Here's part of the test code:

public class TestDialogsActivity extends Activity implements DlgConfirmEvent
{

   @Override
  public void onCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    Button btShowMessage = (Button) findViewById(R.id.btShowMessage);
    btShowMessage.setOnClickListener(new View.OnClickListener() {
      public void onClick(View view) 
      {
        DialogMes dlgMes = new DialogMes( TestDialogsActivity.this,"Message","life is good") ;                
      }  
    }); 

I also implemented a modal Yes/No dialog following the interface approach suggested above by JohnnyBeGood, and it works pretty good too.

Correction:

My answer is not relevant to the question that I misunderstood. For some reason, I interpreted M. Romain Guy "you don't want to do that" as a no no to modal dialogs. I should have read: "you don't want to do that...this way".

I apologize.

 
5

Developers of Android and iOS decided that they are powerful and smart enough to reject Modal Dialog conception (that was on market for many-many years already and didn't bother anyone before), unfortunately for us. I believe that there is work around for Android - since you can show dialog from non-ui thread using Runnable class, there should be a way to wait in that thread (non-ui) until dialog is finished.

Edit: Here is my solution, it works great:

    int pressedButtonID;
    private final Semaphore dialogSemaphore = new Semaphore(0, true);
    final Runnable mMyDialog = new Runnable()
    {
        public void run()
        {
            AlertDialog errorDialog = new AlertDialog.Builder( [your activity object here] ).create();
            errorDialog.setMessage("My dialog!");
            errorDialog.setButton("My Button1", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    pressedButtonID = MY_BUTTON_ID1;
                    dialogSemaphore.release();
                    }
                });
            errorDialog.setButton2("My Button2", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    pressedButtonID = MY_BUTTON_ID2;
                    dialogSemaphore.release();
                    }
                });
            errorDialog.setCancelable(false);
            errorDialog.show();
        }
    };

    public int ShowMyModalDialog()  //should be called from non-UI thread
    {
        pressedButtonID = MY_BUTTON_INVALID_ID;
        runOnUiThread(mMyDialog);
        try
        {
            dialogSemaphore.acquire();
        }
        catch (InterruptedException e)
        {
        }
        return pressedButtonID;
    }
 
0
UserSelect =null

AlertDialog.Builder builder = new Builder(ImonaAndroidApp.LoginScreen);
            builder.setMessage("you message");
            builder.setPositiveButton("OK", new OnClickListener() {

                @Override
                public void onClick(DialogInterface dialog, int which) {
                    UserSelect = true ;

                }
            });

            builder.setNegativeButton("Cancel", new OnClickListener() {

                @Override
                public void onClick(DialogInterface dialog, int which) {
                    UserSelect = false ;

                }
            });
           // in UI thread
        builder.show();             
        // wait until the user select
            while(UserSelect ==null);
 
  •  
    That last line of yours, "while (UserSelect == null);" - that one is a killer. Your phone will enter into an endless loop, and will actually heat up the CPU for no reason and empty the battery, and make the entire phone slow.   Oct 15, 2020 at 12:24
1

Rewriting:

There are radical differences between mobile and desktop environment, as well as between the way in which applications were developed to a handful of years ago and today:

a) mobile devices need to conserve energy. Part of the value they offer. So you need to save resources. Threads are an expensive resource. Halt the progress of a thread is an unacceptable waste of this resource.

b) Nowadays the user is much more demanding. To assist them, we believe that it deserves to have full working CPU and the smallest possible expenditure of energy. Its application is not alone on the device, there is an unknow number of another apps running at same time and your app is not necessarily the most urgent.

c) system-level locks are not an option: a mobile device works with a number of events and services in the background and it is not right for any of them can be locked by an application.

Think about the user receiving a phone call while your "system lock" is working...

Based on the above facts, the answer to the proposed question are:

  • There is a viable way to build a dialog that blocks the main thread until a response from the user?

No. Workarounds do user experience get worse and it may make the mistake of blaming the system itself. This is unfair and penalizes the platform and all its developers.

  • Is there a way to block the entire system with a dialogue?

No. This is strictly forbidden on the platform. No application can interfere with the operation of the system or other applications.

  • I need to refactor my application, or rethink my way of programming to suit me on the Android mobile system architecture.

Yes. Including this aspect.

 
0

I am using Xamarin.Android (MonoDroid), and I have requirments for developing UI Blocking confirm box. I am not going to argue with the client because I can see good reasons for why they want that (details here), so I need to implement this. I tried @Daniel and @MindSpiker above, but these did not work on MonoForAndroid, the moment the message is sent between the threads, the app is crashed. I assume it is something to do with Xamarin mapping.

I ended up creating a separate thread from the UI thread and then blocking that and waiting for the user response as follows:

// (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;

Full details on how to do this can be found in my blog post here

 
0

let me as a thank you to the StackOverflow community, share something nice with you, where I have used the above example in Android code for HTTP calls, which now turns MODAL instead of the usual extra thread and the complexities of merging the threads by use of the a bit peculiar threading. (This is working in our app right now - 15 Oct 2020)

public JSONArray genericHttpModal(Context context, final String url, final JSONObject request) {

    this.url = url;    
    genericHttpRequestQueue = Volley.newRequestQueue(context);      
    class MyJsonReturn {
        JSONArray returnJsonArray;

        public void set(JSONArray i) {
            returnJsonArray = i;
        }

        public void set(String i) {
            try {
                returnJsonArray.put(0, i);
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }

        public JSONArray get() {
            return returnJsonArray;
        }
    }

    final MyJsonReturn mymy = new MyJsonReturn();

    // Positive Response / HTTP OK.
    final Handler handler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            throw new RuntimeException();
        }
    };

    final Response.Listener responseListener = new Response.Listener<String>() {
        @Override
        public void onResponse(String response) {
            try {
                mymy.set(new JSONArray(response));
            } catch (JSONException e) {
                mymy.set("[{\"JSONException:\"" + e.getMessage() + "\"}]");
            }
            handler.sendMessage(handler.obtainMessage());
        }
    };

    // Negative Response / HTTP NOT OK
    final Response.ErrorListener errorListener = new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
            result = "fail";

            try {
                mymy.set(new JSONArray("[{\"JSONException:\"" + result + "\"}]"));
            } catch (JSONException e) {
                mymy.set("[{\"JSONException:\"" + e.getMessage() + "\"}]");
            }
            handler.sendMessage(handler.obtainMessage());
        }
    };


    final StringRequest httpRequest = new StringRequest(Request.Method.POST, URL_POINTER + url,
            responseListener,
            errorListener) {

        // Here the mRequestQueue handler will get the parameters for this request here.
        // Ref: https://stackoverflow.com/questions/33573803/how-to-send-a-post-request-using-volley-with-string-body#33578202
        // Ref: Multi Threaded solution 14 Oct 2020 (David Svarrer) : https://stackoverflow.com/questions/2028697/dialogs-alertdialogs-how-to-block-execution-while-dialog-is-up-net-style (This stackoverflow here)
        @Override
        protected java.util.Map<String, String> getParams() throws AuthFailureError {
            return jsonObjectToMap(request);
        }
    };
    httpRequest.setShouldCache(false); // Elijah: Could work on dropping the cache !!!
    genericHttpRequestQueue.add(httpRequest);    

    try {
        Looper.loop();
    } catch (RuntimeException re) {
    }
    return mymy.get();
}
 
0

Hu! Damn I was struggling whole day for this. Somehow I got workaround though I would't recommend it. You will have to use Handler to achieve this.
call getDialogBack function to get

boolean result =  getDialogBack(this);
Log.d(TAG, "onCreate: "+result);   

In getDialogBack write dialog and handler code to make it synchronous

public int getDialogBack(Context context,int i) {
        final Handler handler = new Handler(Looper.getMainLooper()) {

            @Override
            public void handleMessage(Message mesg) {
                throw new RuntimeException();
            }
        };

        AlertDialog.Builder alert = new AlertDialog.Builder(context);
        alert.setTitle("Title");
        alert.setMessage("Message");
        alert.setPositiveButton("Want to print next", new
                DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        handler.sendMessage(handler.obtainMessage());
                    }
                });
        alert.setNegativeButton("Return False", new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int id) {
                handler.sendMessage(handler.obtainMessage());
            }
        });
        alert.show();

        try {
            Looper.loop();
        } catch (RuntimeException e) {
        }

        return true;
    }

Asked 11 years ago
Viewed 105k times
 
56

These days I'm working on simulating modal dialog in Android. I've googled a lot, there's much discussions but sadly there's not much options to get it modal. Here's some background,
Dialogs, Modal Dialogs and Blockin
Dialogs / AlertDialogs: How to "block execution" while dialog is up (.NET-style)

There's no straight way to get modal behavior, then I came up with 3 possible solutions,
1. Use a dialog-themed activity, like this thread said, but I still can't make main activity truly wait for dialog-activity return. Main activity turned to stop status and got restarted then.
2. Build one worker thread, and use thread synchronization. However, it's a huge refactoring job for my app, now I have a single main activity and a service both in main UI thread.
3. Take over event handling within a loop when there is a modal dialog up, and quit loop when dialog gets closed. Actually it's the way to build a real modal dialog like what it exactly does in Windows. I still haven't prototyped this way.

I'd still like to simulate it with a dialog-themed activity,
1. start dialog-activity by startActivityForResult()
2. get result from onActivityResult()
Here's some source

public class MainActivity extends Activity {

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    MyView v = new MyView(this);
    setContentView(v);
}

private final int RESULT_CODE_ALERT = 1;
private boolean mAlertResult = false;
public boolean startAlertDialog() {
    Intent it = new Intent(this, DialogActivity.class);
    it.putExtra("AlertInfo", "This is an alert");
    startActivityForResult(it, RESULT_CODE_ALERT);

    // I want to wait right here
    return mAlertResult;
}

@Override
protected void onActivityResult (int requestCode, int resultCode, Intent data) {
    switch (requestCode) {
    case RESULT_CODE_ALERT:
        Bundle ret = data.getExtras();
        mAlertResult = ret.getBoolean("AlertResult");
        break;
    }
}
}

The caller of startAlertDialog will block execution and expect returned result. But startAlertDialog returned immediately of course, and main activity went into STOP status while DialogActivity was up.

So the question is, how to make main activity really wait for result?
Thanks.

 
  •  
  • 2
     
     
    I can't really believe Android sucks on this very simple things.   Apr 29, 2015 at 16:33
  •  
    The first link in your question explains that Android does have model dialogs (but not blocking ones). Your question be a lot clearer if you changed your terminology to reflect what you were really asking for - thread blocking. 
    – LarsH
     Mar 31, 2016 at 19:29
  •  
    Use a broadcast receiver that calls the next method in the chain... dead end the code until the method is inoked. 
    – me_
     Jul 27, 2018 at 1:29
  •  
    Shame for Google that Android does not support modal dialogs. I believe they should solve this problem. We as programmers should persuade Google to do the right things rather than to re-invent a modal like dialog. The modal dialog is already there and implemented by Microsoft in Windows. Why not implemented by Google? Because Google engineers are lazy ;-)   Jun 5, 2019 at 11:08 

11 Answers

1

It's not difficult.

Assume you have a flag on your owner activity (named waiting_for_result), whenever your activity is resumed:

public void onResume(){
    if (waiting_for_result) {
        // Start the dialog Activity
    }
}

This guaranteed the owner activity, unless the modal dialog is dismissed, whenever it try to get focus will pass to the modal dialog activity.

 
  • 1
     
     
    I've updated first thread with some source. I didn't follow you here, is there something to do with onResume? thx. 
    – fifth
     May 25, 2011 at 7:20
 
17

It is not possible the way you planned. First, you are not allowed to block the UI thread. Your application will be terminated. Second, need to handle the lifecycle methods that are called when another activity is started with startActivity (your original acitvity will be paused while the other activity is running). Third, you probably could somehow hack it by using startAlertDialog() not from the UI thread, with thread synchronization (like Object.wait()) and some AlertDialog. However, I strongly encourage you to not do this. It is ugly, will certainly break and it's just not the way things are intended to work.

Redesign your approach to capture the asynchronous nature of these events. If you want for example some dialog which asks the user for a decsision (like accepting the ToS or not) and do special actions based on that decision create a dialog like this:

AlertDialog dialog = new AlertDialog.Builder(context).setMessage(R.string.someText)
                .setPositiveButton(android.R.string.ok, new OnClickListener() {

                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                        // Do stuff if user accepts
                    }
                }).setNegativeButton(android.R.string.cancel, new OnClickListener() {

                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                        // Do stuff when user neglects.
                    }
                }).setOnCancelListener(new OnCancelListener() {

                    @Override
                    public void onCancel(DialogInterface dialog) {
                        dialog.dismiss();
                        // Do stuff when cancelled
                    }
                }).create();
dialog.show();

Then have two methods handling positive or negative feedback accordingly (i.e. proceeding with some operation or finishing the activity or whatever makes sense).

 
  • 3
     
     
    thanks. I completely agreed everything you presented above. Unfortunately, it's a MUST requirement for me, and the real situation is much more complicated, a long story. Modal behavior is basically conflict against design of Android, we all know, but... Anyway, I'm trying looking for somewhat elegant way to solve it :( is Solution 3 an option? 
    – fifth
     May 25, 2011 at 9:59 
  •  
    Is it possible to have rich controls (like buttons, textviews and editviews) in such a dialog? which can wait until user provides something as input, and focuses back to activity which was running. 
    – Kushal
     Jun 12, 2012 at 15:34
  • 4
     
     
    I take issue with the way this was worded "It is not possible the way you planned. First, you are not allowed to block the UI thread." -- one need not block the UI thread to display a Modal dialog -- Windows will kill your apps if they block the UI thread as well, but it doesn't stop them from having modal dialogs -- those apps continue to handle messages and be responsive while forcing the user to deal with the modal dialog before returning to the parent window (and no, the parent windows aren't blocking either -- they handle all of their events normally).   Aug 3, 2013 at 1:20
  •  
    @BrainSlugs83: "Windows will kill your apps if they block the UI thread as well.." - No it wont.   Apr 17, 2015 at 7:11
  • 1
     
     
    @BrainSlugs83: I think the problem was not with Stephan's wording but with the OP's. Stephan didn't say that modal dialogs require blocking; that's the OP. 
    – LarsH
     Mar 31, 2016 at 19:19
6

Finally I ended up with a really straight and simple solution.

People who's familiar with Win32 programming possibly knows how to implement a modal dialog. Generally it runs a nested message loop (by GetMessage/PostMessage) when there is a modal dialog up. So, I tried to implement my own modal dialog in this traditional way.

At the first, android didn't provide interfaces to inject into ui thread message loop, or I didn't find one. When I looked into source, Looper.loop(), I found it's exactly what I wanted. But still, MessageQueue/Message haven't provided public interfaces. Fortunately, we have reflection in java. Basically, I just copied exactly what Looper.loop() did, it blocked workflow and still properly handled events. I haven't tested nested modal dialog, but theoretically it would work.

Here's my source code,

public class ModalDialog {

private boolean mChoice = false;        
private boolean mQuitModal = false;     

private Method mMsgQueueNextMethod = null;
private Field mMsgTargetFiled = null;

public ModalDialog() {
}

public void showAlertDialog(Context context, String info) {
    if (!prepareModal()) {
        return;
    }

    // build alert dialog
    AlertDialog.Builder builder = new AlertDialog.Builder(context);
    builder.setMessage(info);
    builder.setCancelable(false);
    builder.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int id) {
            ModalDialog.this.mQuitModal = true;
            dialog.dismiss();
        }
    });

    AlertDialog alert = builder.create();
    alert.show();

    // run in modal mode
    doModal();
}

public boolean showConfirmDialog(Context context, String info) {
    if (!prepareModal()) {
        return false;
    }

    // reset choice
    mChoice = false;

    AlertDialog.Builder builder = new AlertDialog.Builder(context);
    builder.setMessage(info);
    builder.setCancelable(false);
    builder.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int id) {
            ModalDialog.this.mQuitModal = true;
            ModalDialog.this.mChoice = true;
            dialog.dismiss();
        }
    });

    builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int id) {
            ModalDialog.this.mQuitModal = true;
            ModalDialog.this.mChoice = false;
            dialog.cancel();
        }
    });

    AlertDialog alert = builder.create();
    alert.show();

    doModal();
    return mChoice;
}

private boolean prepareModal() {
    Class<?> clsMsgQueue = null;
    Class<?> clsMessage = null;

    try {
        clsMsgQueue = Class.forName("android.os.MessageQueue");
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
        return false;
    }

    try {
        clsMessage = Class.forName("android.os.Message");
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
        return false;
    }

    try {
        mMsgQueueNextMethod = clsMsgQueue.getDeclaredMethod("next", new Class[]{});
    } catch (SecurityException e) {
        e.printStackTrace();
        return false;
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
        return false;
    }

    mMsgQueueNextMethod.setAccessible(true);

    try {
        mMsgTargetFiled = clsMessage.getDeclaredField("target");
    } catch (SecurityException e) {
        e.printStackTrace();
        return false;
    } catch (NoSuchFieldException e) {
        e.printStackTrace();
        return false;
    }

    mMsgTargetFiled.setAccessible(true);
    return true;
}

private void doModal() {
    mQuitModal = false;

    // get message queue associated with main UI thread
    MessageQueue queue = Looper.myQueue();
    while (!mQuitModal) {
        // call queue.next(), might block
        Message msg = null;
        try {
            msg = (Message)mMsgQueueNextMethod.invoke(queue, new Object[]{});
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

        if (null != msg) {
            Handler target = null;
            try {
                target = (Handler)mMsgTargetFiled.get(msg);
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }

            if (target == null) {
                // No target is a magic identifier for the quit message.
                mQuitModal = true;
            }

            target.dispatchMessage(msg);
            msg.recycle();
        }
    }
}
}

Hopefully this would help.

 
  • 17
     
     
    DO NOT DO THIS. This code is using private APIs. It is OBVIOUSLY using private APIs. Anybody doing this can expect their app to break. 
    – hackbod
     Jun 2, 2011 at 4:34
  • 2
     
     
    This code is embarrassing. Nobody in their right mind should be using these sorts of techniques.   Jun 2, 2011 at 17:18
  • 10
     
     
    this just shows you that android framework is poorly designed in the first place. How can you not have a simple mechanism for modal dialogs???!   Sep 21, 2011 at 19:34 
  • 1
     
     
    The main trouble with using another Activity as a dialog is that you can't distinguish between losing focus for the whole application and losing focus because another activity in your application is on top on onPause(). In that case, using a modal-like dialog may make sense. Especially if the client requirements have changed but you're not "enterprise" and don't have the budget for 3 months of refactoring... 
    – Torp
     Jun 30, 2013 at 6:54
  • 3
     
     
    Even though this is bad programming, I upvoted just for the proof of concept.   Sep 19, 2013 at 18:20
8

Developers of Android and iOS decided that they are powerful and smart enough to reject Modal Dialog conception (that was on market for many-many years already and didn't bother anyone before), unfortunately for us.

Here is my solution, it works great:

    int pressedButtonID;
    private final Semaphore dialogSemaphore = new Semaphore(0, true);
    final Runnable mMyDialog = new Runnable()
    {
        public void run()
        {
            AlertDialog errorDialog = new AlertDialog.Builder( [your activity object here] ).create();
            errorDialog.setMessage("My dialog!");
            errorDialog.setButton("My Button1", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    pressedButtonID = MY_BUTTON_ID1;
                    dialogSemaphore.release();
                    }
                });
            errorDialog.setButton2("My Button2", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    pressedButtonID = MY_BUTTON_ID2;
                    dialogSemaphore.release();
                    }
                });
            errorDialog.setCancelable(false);
            errorDialog.show();
        }
    };

    public int ShowMyModalDialog()  //should be called from non-UI thread
    {
        pressedButtonID = MY_BUTTON_INVALID_ID;
        runOnUiThread(mMyDialog);
        try
        {
            dialogSemaphore.acquire();
        }
        catch (InterruptedException e)
        {
        }
        return pressedButtonID;
    }
 
  •  
    Thanks, this did the trick for me. As for a use case: I need a modal dialog that blocks input because it's used for an assert triggered by C++. The dialog must block the UI thread because the thread might trigger more asserts.   Jul 28, 2014 at 13:01
1

One solution is :

  1. Put all code for each selected button into the listener of each button.
  2. alert.show(); must be the last code line in the function calling the Alert. Any code after this line will not wait to close the Alert, but will execute immediately.

Hope Help!

 
6

This works for me: create an Activity as your dialog. Then,

  1. Add this to your manifest for the activity:

    android:theme="@android:style/Theme.Dialog"

  2. Add this to onCreate of your activity

    setFinishOnTouchOutside (false);

  3. Override onBackPressed in your activity:

    @Override public void onBackPressed() { // prevent "back" from leaving this activity }

The first gives the activity the dialog look. The latter two make it behave like a modal dialog.

 
73

I got a modal Dialog while using:

setCancelable(false);

on the DialogFragment (not on the DialogBuilder).

 
  • 9
     
     
    Works like a charm. Example: final AlertDialog.Builder builder = new AlertDialog.Builder(this).setCancelable(false); 
    – slott
     Jun 3, 2014 at 15:07
  • 2
     
     
    This doesn't work. What it is meant with modal Dialog, is the fact, that the UI Thread stops working till the dialog is dismissed, which is not true with this code   Mar 26, 2017 at 19:46
  •  
    As mentioned this should be done on dialog fragment if your dialog is a separate fragment public class MyCustomDialog extends DialogFragment{ .... public Dialog onCreateDialog(Bundle savedInstanceState) { setCancelable(false); ...}   Jan 6, 2018 at 7:05 
  •  
    it is also valid extending the android base Dialog class   Jun 21, 2018 at 13:40
  • 2
     
     
    this does not halt execution of code, it prevents the dialog from being cancelled on back pressed.... the code can be truly halted by dead ending at dialogFragment.show(fragmentTransaction, TAG); and using an onClickListener to send a broadcast intent calling the method required after the dialog has been dismissed. 
    – me_
     Jul 27, 2018 at 2:25
2

As hackbod and others have pointed out, Android deliberately doesn't provide a method for doing nested event loops. I understand the reasons for this, but there are certain situations that require them. In our case we have our own virtual machine running on various platforms and we wanted to port it to Android. Internally there a lot of places where it requires a nested event loop, and it isn't really feasible to rewrite the whole thing just for Android. Anyway, here is a solution (basically taken from How can I do non-blocking events processing on Android?, but I have added a timeout):

private class IdleHandler implements MessageQueue.IdleHandler
{
    private Looper _looper;
    private int _timeout;
    protected IdleHandler(Looper looper, int timeout)
    {
        _looper = looper;
        _timeout = timeout;
    }

    public boolean queueIdle()
    {
        _uiEventsHandler = new Handler(_looper);
        if (_timeout > 0)
        {
            _uiEventsHandler.postDelayed(_uiEventsTask, _timeout);
        }
        else
        {
            _uiEventsHandler.post(_uiEventsTask);
        }
        return(false);
    }
};

private boolean _processingEventsf = false;
private Handler _uiEventsHandler = null;

private Runnable _uiEventsTask = new Runnable()
{
    public void run() {
    Looper looper = Looper.myLooper();
    looper.quit();
    _uiEventsHandler.removeCallbacks(this);
    _uiEventsHandler = null;
    }
};

public void processEvents(int timeout)
{
    if (!_processingEventsf)
    {
        Looper looper = Looper.myLooper();
        looper.myQueue().addIdleHandler(new IdleHandler(looper, timeout));
        _processingEventsf = true;
        try
        {
            looper.loop();
        } catch (RuntimeException re)
        {
            // We get an exception when we try to quit the loop.
        }
        _processingEventsf = false;
     }
}
 
  •  
    Can you explain what you mean by nested event loop  Sep 29, 2014 at 13:24
  • 1
     
     
    It means you have two event loop calls on your callstack. Android provides it's own event loop (the main Looper, which is created automatically). If you want to have a modal dialog or similar, you need to create your own event loop, so you will end up with two nested event loops on the callstack.   Sep 30, 2014 at 14:29
3

I have a similar solution like fifth, but its a little bit simpler and doesn't need reflection. My thinking was, why not use an exception to exit the looper. So my custom looper reads as follows:

1) The exception that is thrown:

final class KillException extends RuntimeException {
}

2) The custom looper:

public final class KillLooper implements Runnable {
    private final static KillLooper DEFAULT = new KillLooper();

    private KillLooper() {
    }

    public static void loop() {
        try {
            Looper.loop();
        } catch (KillException x) {
            /* */
        }
    }

    public static void quit(View v) {
        v.post(KillLooper.DEFAULT);
    }

    public void run() {
        throw new KillException();
    }

}

The use of the custom looper is quite simple. Suppose you have a dialog foo, then simply do the following where you want to call the dialog foo modally:

a) When calling into foo:

foo.show();
KillLooper.loop();

Inside the dialog foo, when you want to exit, you simply call the quit method of the custom looper. This looks as follows:

b) When exiting from foo:

dismiss();
KillLooper.quit(getContentView());

I have recently seen some problems with 5.1.1 Android, do not call a modal dialog from main menu, instead post an event that calls the modal dialog. Without posting the main menu will stall, and I have seen Looper::pollInner() SIGSEGVs in my app.

 
1

I am not sure if this is 100% modal, as you can click on some other component to close the dialog box, but I got confused with the loops constructs and so I offer this as another possibility. It worked nicely for me, so I would like to share the idea. You can create and open the dialog box in one method and then close it in the callback method and the program will wait for the dialog reply before executing the callback method. If you then run the rest of the callback method in a new thread, the dialog box will also close first, before the rest of the code is executed. The only thing you need to do is to have a global dialog box variable, so that different methods can acccess it. So something like the following can work:

public class MyActivity extends ...
{
    /** Global dialog reference */
    private AlertDialog okDialog;

    /** Show the dialog box */
    public void showDialog(View view) 
    {
        // prepare the alert box
        AlertDialog.Builder alertBox = new AlertDialog.Builder(...);

        ...

        // set a negative/no button and create a listener
        alertBox.setNegativeButton("No", new DialogInterface.OnClickListener() {
            // do something when the button is clicked
            public void onClick(DialogInterface arg0, int arg1) {
                //no reply or do nothing;
            }
        });

        // set a positive/yes button and create a listener
        alertBox.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
            // do something when the button is clicked
            public void onClick(DialogInterface arg0, int arg1) {
                callbackMethod(params);
            }
        });

        //show the dialog
        okDialog = alertBox.create();
        okDialog.show();
    }


    /** The yes reply method */
    private void callbackMethod(params)
    {
        //first statement closes the dialog box
        okDialog.dismiss();

        //the other statements run in a new thread
        new Thread() {
            public void run() {
                try {
                    //statements or even a runOnUiThread
                }
                catch (Exception ex) {
                    ...
                }
            }
        }.start();
    }
}
 
0

Use a BroadcastReceiver that calls the next method required in the activity.

Dead-end the activity code at dialogFragment.show(fragmentTransaction, TAG); and continue it in onReceive()--i'm not 100% positive but I would lay money that startActivityForResult(); is based on exactly this concept.

Until that method is invoked from the receiver, the code will stand in wait for user interaction without ANR.

DialogFragment's onCreateView Method

private static final String ACTION_CONTINUE = "com.package.name.action_continue";

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
    View v = inflater.inflate(R.layout.fragment_dialog, container, false);
        Button ok_button = v.findViewById(R.id.dialog_ok_button);
        ok_button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent i = new Intent();
                i.setAction(ACTION_CONTINUE);
                getActivity().getApplicationContext().sendBroadcast(i);
                dismiss();
            }
        });


    return v;
}

This method depends on building a DialogFrament extension class and calling an instance of that class through the activity.

However...

Simple, clear, easy and truly modal.

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章