44

I would like to implement a method that displays a dialog, waits until the dialog is dismissed, and then returns a result depending on the dialog contents. Is this possible?

public String getUserInput()
{
    //do something to show dialog
    String input = //get input from dialog
    return input;
}

I am actually trying to implement an interface which has method "public String getUserInput()", where the returned String must be retrieved via dialog. This is easily done in java, seems impossible in android?

EDIT: Posting some sample code as requested in comment

getInput() must be called from a background thread (I call it from an AsynchTask). getInput() displays a dialog and calls wait. When the ok button is pressed on the dialog, the dialog sets the user input in a member variable and calls notify. When notify is called, getInput() continues and returns the member variable.

String m_Input;

public synchronized String getInput()
{
    runOnUiThread(new Runnable() 
    {
        @Override
        public void run() 
        {
            AlertDialog.Builder alert = new AlertDialog.Builder(context);
            //customize alert dialog to allow desired input
            alert.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int whichButton)
            {
                          m_Input = alert.getCustomInput();
                          notify();
            }
        });
        alert.show();   
        }
    });

    try 
    {
         wait();
    } 
    catch (InterruptedException e) 
    {
    }

    return m_Input;
}
Mr_and_Mrs_D
  • 32,208
  • 39
  • 178
  • 361
ab11
  • 19,770
  • 42
  • 120
  • 207
  • 6
    You cannot wait for the user in a method called on the UI thread. So you must either switch to an event-driven model of program flow, or call this from a background thread which can wait until an onWhatever() method called on the UI thread in response to user action updates whatever your background method was waiting for. – Chris Stratton Dec 07 '10 at 20:42
  • I don't really understand, could you explain how I would call this from a background thread, and then return my string up onWhatever()? – ab11 Dec 07 '10 at 20:46
  • 1
    Because if you'd be blocking the UI thread for more than 3(?), I think it was three seconds you'd get an ANR dialog popup asking if you want to close the application. Never run blocking stuff on the UI thread in Android. – Octavian Helm Dec 07 '10 at 20:56
  • Ok, so, if I can insure that getUserInput() is called off the UI thread, then I would be able to show the dialog and wait for the response? – ab11 Dec 07 '10 at 21:06
  • You can use this to show the dialog and block the background thread: https://github.com/jrummyapps/blocking-dialog – Jared Rummler Feb 16 '17 at 20:36

8 Answers8

33

Is this possible?

No. There is no blocking UI model in Android. Everything is asynchronous.

UPDATE

In response to some of your comments on the question itself, you cannot display a UI from a background thread. As I wrote in this answer, there is no blocking UI model in Android. Just put your code in the button handler for your dialog that you want to have executed when the dialog is accepted, such as in this sample project.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • 3
    Maybe its the answer someone didn't want to hear? I've had it happen a couple of times. – blindstuff Dec 07 '10 at 21:01
  • 2
    You cannot directly display a UI from a background thread, but you can ask from the background thread that the UI thread to display it for you. – Chris Stratton Dec 07 '10 at 21:39
  • @Chris Stratton: Very true. It seems a bit tortured, though, to fork a thread for the sole purpose of pretending to have a blocking UI model. – CommonsWare Dec 07 '10 at 21:41
  • 2
    I wouldn't recommend it unless the event driven model is just too much of a headache to come to terms with, or unless dealing with a large pieces of legacy code, especially legacy code minimally ported to build as a jni library. – Chris Stratton Dec 07 '10 at 21:57
  • @Chris Stratton: Ah...the JNI angle I might buy, though I seem to recall that Swing didn't have blocking dialogs, either. But, my memory is fuzzy, and I haven't a clue how SWT handled it. – CommonsWare Dec 07 '10 at 22:03
  • I have a class with my custom AlertDialog that I'm calling from multiple activities. I want it to start different things after it finishes for each activity. I can't find a way to pass methods to the class from the activities so, although I understand the better practice would be to put code in the onClickListener, I don't think it's practical for my situation. – sajattack Sep 02 '12 at 22:09
  • @sajattack: In Java, you don't pass methods, you pass objects. So, you pass a listener from the activity to the dialog to be invoked when the dialog is accepted or dismissed or whatever. – CommonsWare Sep 02 '12 at 22:21
  • Copying the Dialog's onClickListener to each activity only to change what it calls in one case (accept button clicked) doesn't seem like the right way to go. I put the Dialog in it's own class to avoid copying code. – sajattack Sep 06 '12 at 00:54
  • +1 for "Just put your code in the button handler for your dialog that you want to have executed when the dialog is accepted" – Eido95 Apr 18 '17 at 21:21
  • Why I never think of this. Nice and simple –  Sep 01 '19 at 02:18
14

The right way to do this is an event driven program model, ie, "don't call us, we'll call you".

In simple console mode programming, your code tends to call blocking input functions, which don't return until you've gotten a value.

Many gui programming environments work differently - your code is not normally running, but instead it's called by the operating system / window manager when something of potential interest happens. You do something in response to this and promptly return - if you do not, you can't be notified of anything else since the OS has no way to contact you until you return. (In comparison to win32, it's as if the message loop is implemented by Android, and you only get to write the rest of the code that the message loop calls with events - if you don't return promptly, the message loop hangs)

As a result, you need to rethink your concept of program flow. Instead of writing out a to-do list as a simple series of statements, think about it as a sequence of actions which depend on each other and on input. Remember what action you are currently on in a state variable. When you get called with an event such as user input, see if that event means it's now possible to move on to the next step, and if so update your state variable before promptly returning to the OS in order to be able to receive the next event. If the event wasn't what you needed, then just return without updating your state.

If this model won't work for you, what you can do is write a background thread of program logic which runs like a console-mode application using blocking input. But your input functions will really just wait on a flag or something to be notified that input is available. Then on your UI thread where Android delivers events, you update the flag and promptly return. The background thread sees the flag has changed to indicate that data has been provided, and continues execution. (Something like an android terminal emulator takes this to an extreme, where the background component is actually another process - a console mode linux one, and it gets its input using potentially blocking I/O from pipes. The java component accepts android UI events and stuffs characters into the stdin pipe and pulls them out of the stdout pipe to display on the screen.)

Chris Stratton
  • 39,853
  • 6
  • 84
  • 117
13

Thanks for all the feedback, I was able to solve this using a background thread along with a wait() and notify(). I recognize this isn't the greatest idea for the given paradigm, but it was necessary to conform to a library that I am working with.

ab11
  • 19,770
  • 42
  • 120
  • 207
6

I had a hard time understanding all the solutions offered above so far so I found my own one.

I wrap the code thats supposed to be performed after the user input is OK'ed in a runnable, like so:

    Runnable rOpenFile = new Runnable() {
        @Override
        public void run() {
            .... code to perform
        }
    }

Then right below that I pass the name of the runnable function to the user dialog method.

    userInput("Open File", rOpenFile);

The userInput method is based on the alertDialog builder like described above. When the user input is Ok'ed it starts the intended runnable.

private void userInput(String sTitle, final Runnable func) {
    AlertDialog.Builder aBuilder = new AlertDialog.Builder(this);

    aBuilder.setTitle(sTitle);
    final EditText input = new EditText(this);
    input.setInputType(InputType.TYPE_CLASS_TEXT);
    aBuilder.setView(input);

    bDialogDone = false;

    aBuilder.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            final String sText = input.getText().toString();
            sEingabe = sText;
            func.run();
        }
    });
    aBuilder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            dialog.cancel();
            sEingabe = "";
        }
    });

    aBuilder.show();
}
JonathanDavidArndt
  • 2,518
  • 13
  • 37
  • 49
Skandalos
  • 123
  • 2
  • 6
1

Something like this would do

/**
 *
 */

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.WindowManager;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;

/**
 * @author 
 */
public class TextEntryActivity extends Activity {
    private EditText et;

    /*
     * (non-Javadoc)
     * @see android.app.Activity#onCreate(android.os.Bundle)
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_text_entry);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND,
                WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
        // title
        try {
            String s = getIntent().getExtras().getString("title");
            if (s.length() > 0) {
                this.setTitle(s);
            }
        } catch (Exception e) {
        }
        // value

        try {
            et = ((EditText) findViewById(R.id.txtValue));
            et.setText(getIntent().getExtras().getString("value"));
        } catch (Exception e) {
        }
        // button
        ((Button) findViewById(R.id.btnDone)).setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                executeDone();
            }
        });
    }

    /* (non-Javadoc)
     * @see android.app.Activity#onBackPressed()
     */
    @Override
    public void onBackPressed() {
        executeDone();
        super.onBackPressed();
    }

    /**
     *
     */
    private void executeDone() {
        Intent resultIntent = new Intent();
        resultIntent.putExtra("value", TextEntryActivity.this.et.getText().toString());
        setResult(Activity.RESULT_OK, resultIntent);
        finish();
    }


}

The launch is:

public void launchPreferedNameEdit() {
    Intent foo = new Intent(this, TextEntryActivity.class);
    foo.putExtra("value", objItem.getPreferedNickname());
    this.startActivityForResult(foo, EDIT_PREFERED_NAME);
}

You get the result by using

protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    switch (requestCode) {
        case EDIT_PREFERED_NAME:
            try {
                String value = data.getStringExtra("value");
                if (value != null && value.length() > 0) {

                }
            } catch (Exception e) {
            }
            break;
        default:
            break;
    }
}
Pentium10
  • 204,586
  • 122
  • 423
  • 502
  • I might be wrong, but I think this won't work at all for the problem I was describing? I have edited my question to clarify the issue. – ab11 Dec 07 '10 at 20:44
  • As you described it's not possible. But my posts provide the way how you can do this with an activity that has an input text. – Pentium10 Dec 07 '10 at 20:47
0

You can think in terms of a state machine where if you initially require first-time user input you can have a flag set to mark "user input needed" or whatever. Then upon processing an event you check that flag and if set you fire up a dialog as the only action for the event and unset the flag. Then from the dialog event handler after handling user input you can call the code normally intended for the case when a dialog is not needed.

0

For your reference, I just made a dialog.

It would show and wait then dismiss.

And I deploy Java wait and notify to make it, this function can be copied and run directly.

private final Object lock = new Lock();
private static final class Lock {}
private void showWaitDialog(final String message, final int time_to_wait) { //ms
    if(this.isFinishing()) return;
    final String TTAG = "[showWaitDialog]";
    Log.d(TTAG, "dialog going to show");
    final ProgressDialog waitProgress = ProgressDialog.show(this, "WARNING", message, true);
    waitProgress.setCancelable(false);
    waitProgress.setOnShowListener(new DialogInterface.OnShowListener() { //callback got the asynchronous
        @Override
        public void onShow(DialogInterface dialog) {
            Log.d(TTAG, "dialog showed");
            synchronized (lock) {
                try {
                    Log.d(TTAG, "main thread going to wait");
                    lock.wait();
                } catch (InterruptedException e) {
                    Log.e(TTAG, e.toString());
                    Log.e(TTAG, "main thread going ahead");
                }
            }
        }
    });
    new Thread(new Runnable() {
        @Override
        public void run() {
            synchronized (lock) {
                try {
                    Thread.sleep(time_to_wait);
                } catch (Exception e) {
                    Log.d(TTAG, e.toString());
                }
                lock.notifyAll();
                Log.d(TTAG, "dialog notified");
                waitProgress.dismiss();
            }
        }
    }).start();
}
Mou
  • 145
  • 1
  • 6
0

CASE: My data was ready to be processes after a preference change listener event and I needed to add a String queried from user. It doesn't appear possible to pop an alert dialog while the options menu is open...so I had to wait. I threw the half complete object into the next activity in the workflow and set its onResume() to check if its placeholder was !null in which case I popped the dialog and finished up the object *"in the button handler of the dialog"*.

Since this is my first post I can't vote for the correct answer given above but want to save anyone else running into this the time and in-elegance of less correct solutions. The dialog is the place.