59

What is the "correct" way to return the values to the calling activity from a complex custom dialog - say, text fields, date or time picker, a bunch of radio buttons, etc, plus a "Save" and "Cancel" button?

Some of the techniques I've seen on the web include:

  • public data members in the Dialog-derived class which can be read by the Activity

  • public "get" accessors . . . " . . " . . "

  • Launching the dialog with an Intent (as opposed to show() ) plus handlers in the Dialog class which take input from the various controls and bundle them up to be passed back to the Activity so when listener hits "Save" the bundle is passed back using ReturnIntent()

  • Listeners in the Activity which process input from the controls that are in the dialog e.g., so the TimePicker or DatePicker's listeners are really in the Activity. In this scheme practically all the work is done in the Activity

  • One Listener in the Activity for the "Save" button and then the Activity directly interrogates the controls in the dialog; the Activity dismisses the dialog.

...plus more that I've already forgotten.

Is there a particular technique that's considered the canonically correct or "best practice" method?

zelanix
  • 3,326
  • 1
  • 25
  • 35
Peter Nelson
  • 5,917
  • 17
  • 43
  • 62

6 Answers6

23

Perhaps I'm mis-understanding your question, but why not just use the built in listener system:

builder.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
    public void onClick(DialogInterface dialog, int id) {
        // run whatever code you want to run here
        // if you need to pass data back, just call a function in your
        // activity and pass it some parameters
    }
})

This is how I've always handled data from dialog boxes.

EDIT: Let me give you a more concrete example which will better answer your question. I'm going to steal some sample code from this page, which you should read:

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

// Alert Dialog code (mostly copied from the Android docs
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Pick a color");
builder.setItems(items, new DialogInterface.OnClickListener() {
    public void onClick(DialogInterface dialog, int item) {
        myFunction(item);
    }
});
AlertDialog alert = builder.create();

...

// Now elsewhere in your Activity class, you would have this function
private void myFunction(int result){
    // Now the data has been "returned" (as pointed out, that's not
    // the right terminology)
}
JJD
  • 50,076
  • 60
  • 203
  • 339
Computerish
  • 9,590
  • 7
  • 38
  • 49
  • 4
    My question is whether there's a best practice method for RETURNING the values. What you've described is how the Activity knows that the user is done. Once the user is done there are lots of ways of returning the values but I don't know whether one of them is best practice. – Peter Nelson Dec 17 '10 at 19:39
  • 1
    It depends how you're creating your dialog, what values you want "Returned", where you're "returning" them to. How are we supposed to answer your question? – Falmarri Dec 17 '10 at 20:00
  • 5
    the issue is with the terminology. you don't "return" from a dialog. it's not a method or a function. it doesn't have a return value. you want to run some code when the user clicks something in your dialog. that code might also close the dialog, but that doesn't mean it's returning a value. just like computerish posted, the way to do that is assign an onclick handler to the "done" and "cancel" buttons in your dialog. among other things, they call .dismissDialog(...), and could of course call methods in your activity. – Jeffrey Blattman Dec 17 '10 at 20:14
  • 1
    It's wrong, since you can't run whatever code in onClick() - you can use there only members of parent activity, stack/auto variables can't be passed to onClick(); – Barmaley Dec 17 '10 at 20:31
  • barmaley - Could you elaborate on why it's wrong? My example is just a theoretical example, so you could be right, but I didn't understand your reasoning. – Computerish Dec 17 '10 at 20:36
  • "It depends how you're creating your dialog, what values you want "Returned", where you're "returning" them" It's a custom dialog not an Alert Dialog, and let's say the values are 3 bools, a calendar time/date and a string. – Peter Nelson Dec 17 '10 at 22:00
  • What term would you prefer besides 'return'? If the user is scheduling an event he causes this dialog to appear, fills it in, and presses "save". Conceptually, to me, the values are returned from the dialog. What would you suggest as a term to disambiguate it with a method return value? – Peter Nelson Dec 17 '10 at 22:07
  • The terminology doesn't really matter as long as everyone knows what you're talking about. How about passing the data back if you really want a better term for it. Or if you want to confuse everyone, how about pseudo-return. :-) – Computerish Dec 17 '10 at 22:44
  • @Computerish: just imagine. You have activity in which you have computed smth important. Then you send those important data to dialog. Depending on what user press: positive or negative data one have to return back to parent activity those modified data. How could you do it in your theoretical example? As soon as you'll try to implement your example you'll understand why it's wrong. – Barmaley Dec 19 '10 at 16:12
  • I think you might be trying to combine my original example with the new answer I gave. The first example is for a positive/negative type dialog and the second is for a selection dialog. Those are two different types of dialogs. As far as modifying the items to select, that shouldn't create an issue, since the activity already knows what the options it used to create the dialog are. – Computerish Dec 19 '10 at 23:44
  • Awesome! This is the least confusing approach I have seen. I understand that this is a functional way of creating dialogs i.e. it uses a builder. Could you also add how one could do this with a class based approach i.e. inheriting the DialogFragment? I can think of passing `this` into the custom dialog class instantiation and then use the public functions of the Activity class. But I'm wondering if there is a way to use the private functions. – sprajagopal May 20 '21 at 12:46
7

For my MIDI app I needed yes/no/cancel confirmation dialogs, so I first made a general StandardDialog class:

public class StandardDialog {

    import android.app.Activity;
    import android.app.AlertDialog;
    import android.content.DialogInterface;
    import android.os.Handler;

    public class StandardDialog {
    public static final int dlgResultOk         = 0;
    public static final int dlgResultYes        = 1;
    public static final int dlgResultNo         = 2;
    public static final int dlgResultCancel     = 3;

    public static final int dlgTypeOk           = 10;
    public static final int dlgTypeYesNo        = 11;
    public static final int dlgTypeYesNoCancel  = 12;

    private Handler mResponseHandler;
    private AlertDialog.Builder mDialogBuilder;
    private int mDialogId;

    public StandardDialog(Activity parent, 
                          Handler reponseHandler, 
                          String title, 
                          String message, 
                          int dialogType, 
                          int dialogId) {

        mResponseHandler = reponseHandler;
        mDialogId = dialogId;
        mDialogBuilder = new AlertDialog.Builder(parent);
        mDialogBuilder.setCancelable(false);
        mDialogBuilder.setTitle(title);
        mDialogBuilder.setIcon(android.R.drawable.ic_dialog_alert);
        mDialogBuilder.setMessage(message);
        switch (dialogType) {
        case dlgTypeOk:
            mDialogBuilder.setNeutralButton("Ok", new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                    mResponseHandler.sendEmptyMessage(mDialogId + dlgResultOk);
                }
            });         
            break;

        case dlgTypeYesNo:
        case dlgTypeYesNoCancel:
            mDialogBuilder.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                    mResponseHandler.sendEmptyMessage(mDialogId + dlgResultYes);
                }
            });         
            mDialogBuilder.setNegativeButton("No", new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                    mResponseHandler.sendEmptyMessage(mDialogId + dlgResultNo);
                }
            });         
            if (dialogType == dlgTypeYesNoCancel) {
                mDialogBuilder.setNeutralButton("Cancel", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        mResponseHandler.sendEmptyMessage(mDialogId + dlgResultCancel);
                    }
                });         
            }
            break;
        }
        mDialogBuilder.show();
    }
}

Next, in my main activity I already had a message handler for the UI updates from other threads, so I just added code for processing messages from the dialogs. By using a different dialogId parameter when I instantiate the StandardDialog for various program functions, I can execute the proper code to handle the yes/no/cancel responses to different questions. This idea can be extended for complex custom dialogs by sending a Bundle of data though this is much slower than a simple integer message.

private Handler uiMsgHandler = new Handler() {

    @Override
    public void handleMessage(Message msg) {
        if (msg != null) {

            // {Code to check for other UI messages here}

            // Check for dialog box responses
            if (msg.what == (clearDlgId + StandardDialog.dlgResultYes)) {
                doClearDlgYesClicked();
            }
            else if (msg.what == (recordDlgId + StandardDialog.dlgResultYes)) {
                doRecordDlgYesClicked();
            }
            else if (msg.what == (recordDlgId + StandardDialog.dlgResultNo)) {
                doRecordDlgNoClicked();
            }
        }
    }
};

Then all I need to do is define the do{Whatever}() methods in the activity. To bring up a dialog, as an example I have a method responding to a "clear recorded MIDI events" button and confirm it as follows:

public void onClearBtnClicked(View view) {
    new StandardDialog(this, uiMsgHandler, 
        getResources().getString(R.string.dlgTitleClear),
        getResources().getString(R.string.dlgMsgClear), 
        StandardDialog.dlgTypeYesNo, clearDlgId);
}

clearDlgId is defined as a unique integer elsewhere. This method makes a Yes/No dialog pop up in front of the activity, which loses focus until the dialog closes, at which time the activity gets a message with the dialog result. Then the message handler calls the doClearDlgYesClicked() method if the "Yes" button was clicked. (I didn't need a message for the "No" button since no action was needed in that case).

Anyway, this method works for me, and makes it easy to pass results back from a dialog.

JJD
  • 50,076
  • 60
  • 203
  • 339
Brian Bailey
  • 71
  • 1
  • 1
  • This works fine for me. But the Android studio states the Handler class should be static or there is a risk of memory leak (yellow text). Convert static it there are huge problems making it work. Any comments? – Jan Bergström Jan 07 '16 at 02:28
5

I'm using following way:

  1. All my activities has one and the same parent Activity (let's say ControlActivity). ControlActivity has private volatile Bundle controlBundle; with appropriate getter/setter
  2. When I start dialog, I used to call dialog thru my own method:

    public void showMyDialog(int id, Bundle bundle)
    {
        this.controlBundle=bundle;
        this.showDialog(id, bundle);
    }
    

So each time I know parameters sent to dialog

  1. When dialog is about to complete, I'm forming in dialog another Bundle with necessary values and then put them thru my Activity bundle setter:

((ControlActivity )this.getOwnerActivity).setControlBundle(bundle);

So in the end when dialog finishes I know value "returned" from dialog. I know that it's not like int retCode=this.showMyDialog(); it's a bit more complex, but it's workable.

JJD
  • 50,076
  • 60
  • 203
  • 339
Barmaley
  • 16,638
  • 18
  • 73
  • 146
  • Barmaley's example is closer to my need for a way to handle more complex data than just an int. But it also illustrates yet another strategy. As I indicated earlier, I'm seeing a remarkably wide range of strategies and as an Android newbie I'm trying to understand which strategies are consistent with "best practice" vs which ones would get a lot of raised eyebrows in a code review. – Peter Nelson Dec 17 '10 at 22:16
2

I've pondered over this myself for a while and eventually the most convenient way I found of doing this is breaking up my activity into various methods that represent each unit of control flow. For example, if the activities within my activity are say : load variables from intent, check for some data, process and proceed if available, if not make a background call, wait for user interaction, start another activity.

I generally pickup the parts that are common, the first two and the last in this case. I'll wrap the first ones in onCreate() and make a separate one for the last... say startAnotherActivity(Data). You can arrange the middle parts so they'll consist of a checkData(Data) (possibly merged into the onCreate()) which calls either processAvailableData(Data) or performBackgroundTask(Data). The background task will perform an operation in the background and return control to onBackgroundTaskCompleted(OtherData).

Now both processAvailableData(Data) and onBackgroundTaskCompleted(OtherData) call the getUserResponse() method which in turn may either call startAnotherActivity(Data) or merge its functions with itself.

I feel this approach gives a number of benefits.

  1. It helps with the data return issue your question points to by "moving forwards" instead of returning data.
  2. It allows easier addition of new functionality. For instance, if we wanted to give the user more options, we could just call the appropriate method from getUserResponse() which could effect the data that is eventually passed to the next activity.
  3. It helps avoid needless flow problems (check out the questions relating to finish() and return on SO) when our intuitive assumption is a certain flow and it turns out to be another.
  4. Helps manage variables better so you don't end up having a lot of class level fields to avoid the variable access problems in anonymous inner classes (onClick(), doInBackground(), etc.).

I'm pretty sure having more methods adds some overhead but its probably offset by the flow, reuse and simplicity advantages you get (would love to hear a compilation expert's views on this).

Saad Farooq
  • 13,172
  • 10
  • 68
  • 94
2

I will explain one solution with the example of two fragments. Imagine there is a SimpleFragment which has just one text field to render a date. Then there is a DatePickerFragment which allows to choose a particular date. What I want is that the DatePickerFragment passes the date value back to the calling SimpleFragment whenever the user confirms her selection.

SimpleFragment

So, first of all we start the DatePickerFragment from within the SimpleFragment:

private DateTime mFavoriteDate; // Joda-Time date

private void launchDatePicker() {
    DatePickerFragment datePickerFragment = new DatePickerFragment();
    Bundle extras = new Bundle();
    // Pass an initial or the last value for the date picker
    long dateInMilliSeconds = mFavoriteDate.getMillis();
    extras.putLong(BundleKeys.LAST_KNOWN_DATE, dateInMilliSeconds);
    datePickerFragment.setArguments(extras);
    datePickerFragment.setTargetFragment(this, SIMPLE_FRAGMENT_REQUEST_CODE);
    datePickerFragment.show(getActivity().getSupportFragmentManager(),
            DatePickerFragment.FRAGMENT_TAG);
}

DatePickerFragment

In the dialog fragment we prepare to pass back the selected date when the user hits the positive button:

public static final String DATE_PICKED_INTENT_KEY = "DATE_PICKED_INTENT_KEY";
public static final int DATE_PICKED_RESULT_CODE = 123;

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
    // ...
    Long dateInMilliSeconds = getArguments().getLong(BundleKeys.LAST_KNOWN_DATE);
    DateTime date = new DateTime(dateInMilliSeconds);
    initializePickerUiControl(date);

    AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(activity);
    dialogBuilder
        .setPositiveButton(R.string.date_picker_positive, (dialog, which) -> {
            // Pass date to caller
            passBackDate();
        })
        .setNegativeButton(R.string.date_picker_negative, (dialog, which) -> {
            // Nothing to do here
        });
    return dialogBuilder.create();
}

private void passBackDate() {
    DateTime dateTime = getDateTimeFromPickerControl();
    Intent intent = new Intent();
    intent.putExtra(DATE_PICKED_INTENT_KEY, dateTime.getMillis());
    getTargetFragment().onActivityResult(
            getTargetRequestCode(), DATE_PICKED_RESULT_CODE, intent);
}

SimpleFragment

Back in the requesting fragment we consume what has been passed back by the dialog:

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == SIMPLE_FRAGMENT_REQUEST_CODE &&
            resultCode == DatePickerFragment.DATE_PICKED_RESULT_CODE) {
        long datePickedInMilliseconds = data.getLongExtra(
                DatePickerFragment.DATE_PICKED_INTENT_KEY, 0);
        mFavoriteDate = new DateTime(datePickedInMilliseconds);
        updateFavoriteDateTextView();
    }
    else {
        super.onActivityResult(requestCode, resultCode, data);
    }
}

References to mattpic who gave an excellent answer before.

Community
  • 1
  • 1
JJD
  • 50,076
  • 60
  • 203
  • 339
1

After quite a bit of research I settled on a callback interface. My code is as follows:

MyFragment.java

public class MyFragment extends Fragment {

...

private void displayFilter() {

    FragmentManager fragmentManager = getFragmentManager();

    FilterDialogFragment filterDialogFragment = new FilterDialogFragment();
    Bundle bundle = new Bundle();
    bundle.putSerializable("listener", new FilterDialogFragment.OnFilterClickListener() {
        @Override
        public void onFilterClickListener() {
            System.out.println("LISTENER CLICKED");

        }
    });
    filterDialogFragment.setArguments(bundle);
    filterDialogFragment.show(fragmentManager, DIALOG_FILTER);

}

MyDialog.java

public class MyDialog extends DialogFragment {

private ImageButton mBtnTest;
private OnFilterClickListener mOnFilterClickListener;

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
    // Get the layout inflater
    LayoutInflater inflater = getActivity().getLayoutInflater();
    View filterLayout = inflater.inflate(R.layout.filter_dialog, null);
    // Inflate and set the layout for the dialog
    // Pass null as the parent view because its going in the dialog layout
    builder.setView(filterLayout)
            .setTitle("Filter");

    Dialog dialog = builder.create();

    mOnFilterClickListener = (OnFilterClickListener) getArguments().getSerializable("listener");

    mBtnTest = (ImageButton)filterLayout.findViewById(R.id.fandb);
    mBtnTest.setOnClickListener(new View.OnClickListener() {
        public void onClick(View v) {
            mOnFilterClickListener.onFilterClickListener();
            dismiss();
        }
    });

    return dialog;
}

public interface OnFilterClickListener extends Serializable {
    void onFilterClickListener();
}

}
Braden Holt
  • 1,544
  • 1
  • 18
  • 32