Implement DialogFragment interface in OnClickListener
Asked Answered
P

5

3

I need to build a DialogFragment which returns user input from the dialog to an activity. The dialog needs to be called in an OnClickListener which gets called when an element in a listview gets clicked.
The return value of the DialogFragment (the input of the user) should be directly available in the OnClickListener in the activity.

I tried to implement this by sticking to the official docs: http://developer.android.com/guide/topics/ui/dialogs.html#PassingEvents

I need something like the following which doesn't work since I don't know how to make the anonymous OnClickListener implement the interface of the CustomNumberPicker class.
As far as I know implementing the interface is necessary in order to get data from the DialogFragment back to the Activity.

Main Activity:

public class MainAcitivity extends ActionBarActivity {
    [...]

    // ArrayAdapter of the Listview
    private class ListViewArrayAdapter extends ArrayAdapter<Exercise> {
        public ListViewArrayAdapter(Context context, ArrayList<Exercise> exercises) {
            super(context, 0, exercises);
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            [...]

            if (convertView == null) {
                convertView = LayoutInflater.from(getContext()).inflate(R.layout.item_workoutdetail, parent, false);
            }

            TextView tvSets = (TextView) convertView.findViewById(R.id.tvWorkoutExerciseSets);
            tvSets.setText(sets.toString());

            // OnClickListener for every element in the ListView
            tvSets.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    // This is where the Dialog should be called and
                    // the user input from the Dialog should be returned
                    DialogFragment numberpicker = new CustomNumberPicker();
                    numberpicker.show(MainActivity.this.getSupportFragmentManager(), "NoticeDialogFragment");
                }

                // Here I would like to implement the interface of CustomNumberPicker
                // in order to get the user input entered in the Dialog
            });

            return convertView;
        }
    }
}

CustomNumberPicker (basically the same as in the docs):

public class CustomNumberPicker extends DialogFragment {

    public interface NoticeDialogListener {
        public void onDialogPositiveClick(DialogFragment dialog);
        public void onDialogNegativeClick(DialogFragment dialog);
    }

    // Use this instance of the interface to deliver action events
    NoticeDialogListener mListener;

    // Override the Fragment.onAttach() method to instantiate the NoticeDialogListener
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        // Verify that the host activity implements the callback interface
        try {
            // Instantiate the NoticeDialogListener so we can send events to the host
            mListener = (NoticeDialogListener) activity;
        } catch (ClassCastException e) {
            // The activity doesn't implement the interface, throw exception
            throw new ClassCastException(activity.toString()
                + " must implement NoticeDialogListener");
        }
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        // Use the Builder class for convenient dialog construction
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        builder.setMessage("Sets")
            .setPositiveButton("set", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        // Return stuff here to the activity?
                    }
                })
                .setNegativeButton("cancle", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        // User cancelled the dialog
                    }
                });
        // Create the AlertDialog object and return it
        return builder.create();
    }
}
Ptyalism answered 20/2, 2015 at 1:7 Comment(4)
do you have a custom layout that you are using?What are you trying to return?Rather what do you want to return?Hennie
what do you want to return when dialog button is clicked? Is it a string or something?Melentha
@Hennie Yes, it's a custom DialogFragment which shows a NumberPicker. I'm trying to return an Integer which is number picked by the user.Ptyalism
You can check this answer.Johannisberger
H
3

Something like this?

public class CustomNumberPicker extends DialogFragment {
    private NoticeDialogListener ndl;

    public interface NoticeDialogListener {
        public void onDialogPositiveClick(DialogFragment dialog);
        public void onDialogNegativeClick(DialogFragment dialog);
    }

    //add a custom constructor so that you have an initialised NoticeDialogListener
    public CustomNumberPicker(NoticeDialogListener ndl){
        super();
            this.ndl=ndl;
    }

    //make sure you maintain an empty constructor
    public CustomNumberPicker( ){
        super();
    }

    // Use this instance of the interface to deliver action events
    NoticeDialogListener mListener;

    // Override the Fragment.onAttach() method to instantiate the NoticeDialogListener
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        //remove the check that verfis if your activity has the DialogListener Attached because you want to attach it into your list view onClick()
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        // Use the Builder class for convenient dialog construction
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        builder.setMessage("Sets")
            .setPositiveButton("set", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        ndl.onDialogPositiveClick(dialog);
                    }
                })
                .setNegativeButton("cancle", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                       ndl.onDialogNegativeClick(dialog);
                    }
                });
        // Create the AlertDialog object and return it
        return builder.create();
    }
}

and then your listView onClick becomes:

tvSets.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    // This is where the Dialog should be called and
                    // the user input from the Dialog should be returned
                    // 
                    // 


                    DialogFragment numberpicker = new CustomNumberPicker(new NoticeDialogListener() {

            @Override
            public void onDialogPositiveClick(DialogFragment dialog) {
                //What you want to do incase of positive click

            }

            @Override
            public void onDialogNegativeClick(DialogFragment dialog) {
               //What you want to do incase of negative click

            }
        };);
                    numberpicker.show(MainActivity.this.getSupportFragmentManager(), "NoticeDialogFragment");
                }

                // Here I would like to implement the interface of CustomNumberPicker
                // in order to get the user input entered in the Dialog
            });

Do read the comments I have added.And it can even be further optimized because you really dont need an entire dialog instance to get the values you need.

EDIT a possible optimization could be:

Changing the Listener interface to :

public interface NoticeDialogListener {
        public void onDialogPositiveClick(String output);
        public void onDialogNegativeClick(String output);
       //or whatever form of output that you want
    }

Then modify the implemented methods accordingly.

Hennie answered 22/2, 2015 at 15:59 Comment(6)
Why not have the adapter implement the callback interface? Much cleaner & manageable,in my opinion.Phelgen
Because the OP specifies that it should be the onClick listener.Basically it can be implemented at adapter,activity,ListView.onClick() or in anonymous listener in the activity.That depends on the choice of the OP.I just modified the Listener so as to make it pluggable anywhereHennie
@Phelgen implementing it at an andapter level would require you to deal with fetching from the list based on position and modifying it.It is much more contextual/inplace to deal with it in the getView isnt it?Hennie
I get an error/warning in AS when trying to add a custom constructor and the doc refer to that setArguments(Bundle) should be used instead. Is this solution "safe"? I get a feeling a screen rotation event could break this?Jarodjarosite
You could go through this answer too see the difference.As for your doubt regarding screen rotation,this answer indicates the validity of your point.You can however easily overcome this issue by implementing onSaveInstance and onRestoreInstance.Hennie
The reason I prefer this design:this will result in a compile time error if you do not implement the required listeners. If you use set arguments,then you face more NPE and runtime errors.In the end,its a design pattern and solely your choice. Android design patterns do not always make sense.Hennie
R
1

You should have your activity, implement your interface (NoticeDialogListener).

public class MainActivity extends ActionBarActivity implements
    NoticeDialogListener{

    @Override
    public void onDialogPositiveClick(DialogFragment dialog){
        //Do something
    }

    @Override
    public void onDialogNegativeClick(DialogFragment dialog){
        //Do some other things
    }

    [...]
}

Then in your button click listeners of the dialog, you use the mListener and call the methods, which is now implemented in the activity and the code will be executed there.

builder.setMessage("Sets")
            .setPositiveButton("set", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        if(mListener != null)
                            mListener.onDialogPositiveClick(CustomNumberPicker.this);
                    }
            });

Also note that you should set the mListener to null in the onDetach() method of your DialogFragment.

@Override
public void onDetach() {
    super.onDetach();
    mListener = null;
}
Reconciliatory answered 22/2, 2015 at 15:17 Comment(1)
Mhh, I think you misunderstood the question or I did not formulate it clearly enough. I need the NoticeDialogListener interface in the OnClickListener of a tvsets object in the ArrayAdapter. This is because I have to attach the return value of a DialogFragment to an element in the ArrayAdapter.Ptyalism
T
0

Here's how it's done: In the Activity where you show the DiaogFragment, set the arguments of the DialogFragment with the desired name value pair. Also make sure that the activity implements the DialogInterface.OnClickListener In the overridded onClick pick up the value from the aforementioned name value pair

public class MainActivity extends AppCompatActivity implements DialogInterface.OnClickListener {

        private static SettingsFragment settingsFragment;
        private Button btnSettings;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main_activity);

            btnSettings = (Button) findViewById(R.id.btnSettings);

            btnSettings.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {

                    settingsFragment = new SettingsFragment();
                    Bundle bundle = new Bundle();
                    bundle.putString("myKey", null);
                    settingsFragment.setArguments(bundle);
                    //Use the commented out line below if you want the click listener to return to a fragment instead of an activity
                    //assuming that this class in a fragment and not an activity
                    //rotateSettingsFragment.setTargetFragment(getActivity().getSupportFragmentManager().findFragmentByTag("TagForThisFragment"), 0);
                    settingsFragment.setTargetFragment(settingsFragment, 0);
                    settingsFragment.setCancelable(true);
                    settingsFragment.show(getSupportFragmentManager(), "SettingsFragment");

                }
            });

        }

        @Override
        public void onClick(DialogInterface dialog, int which) {

            if(getResources().getResourceEntryName(which).equals("btnSettingFragmentClose")) {

                String myValue = settingsFragment.getArguments().getString("myKey");
                dialog.dismiss();

            }

        }

    }

In your DialogFragment declare a DialogInterface.OnClickListener and cast it to the activity in the onAttach. In the event that needs to send back the data to the activity; set the buddle arguments and then call the onClickListener.onClick

public class SettingsFragment extends DialogFragment {

private View rootView;
private Button btnSettingFragmentClose;
private DialogInterface.OnClickListener onClickListener;

public SettingsFragment() {}

/* Uncomment this and comment out on onAttach when you want to return to a fragment instead of an activity.
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    onClickListener = (DialogInterface.OnClickListener) getTargetFragment();

}
*/

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

    rootView = inflater.inflate(R.layout.fragment_settings, container, false);
    btnSettingFragmentClose = (Button) rootView.findViewById(R.id.btnSettingFragmentClose);

    btnSettingFragmentClose.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            getArguments().putString("myKey", "Hello World!");
            onClickListener.onClick(getDialog(), v.getId());

        }
    });

    return rootView;

}


@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);

    try {

        onClickListener = (DialogInterface.OnClickListener) activity;

    }
    catch (ClassCastException e) {

        throw new ClassCastException(activity.toString() + " must implement mainFragmentCallback");

    }

}

}

Tinware answered 14/11, 2015 at 21:53 Comment(0)
J
0

This simple solution works for me:

public class MyActivity implements MyDialogFragment.Listener {

    // ...

    @Override
    public void onMyEvent() {
        // do something here
    }
}

public class MyDialogFragment extends DialogFragment {

    private Listener mCallback;
    public interface Listener {
        void onMyEvent();
    }

    @SuppressLint("RestrictedApi")
    @Override
    public void setupDialog(final Dialog dialog, int style) {
        super.setupDialog(dialog, style);
        View contentView = View.inflate(getContext(), R.layout.dialog_fragment_custom, null);
        dialog.setContentView(contentView);

        mCallback = (Listener) getActivity();

        Button myBtn = (Button) dialog.findViewById(R.id.btn_custom);
        myBtn.setOnClickListener(v -> {
            mCallback.onMyEvent();
            dismiss();
        });
    }
}
Jenevajeni answered 23/5, 2018 at 17:37 Comment(0)
X
-1

As an example you can use DatePickerDialog where DatePickerDialog.OnDateSetListener used to deliver result.

or this is one of my implementations that allow to keep dialog screen open until user not finished with some action or not entered valid data. With custom callback that provide exact interface to this dialog.

public class ConfirmPasswordDialog extends DialogFragment {
    private OnPaswordCheckResult resultListener;
    private TextView passwordView;

    public ConfirmPasswordDialog(OnPaswordCheckResult resultListener){
        this.resultListener = resultListener;
    }

    @Override
    public android.app.Dialog onCreateDialog(Bundle savedInstanceState) {
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
            LayoutInflater inflater = getActivity().getLayoutInflater();
            View dialogView  = inflater.inflate(R.layout.dialog_layout, null);
            builder.setView(dialogView);
            passwordView = (TextView) dialogView.findViewById(R.id.password);
            passwordView.addTextChangedListener(new TextWatcher() {
                @Override
                public void beforeTextChanged(CharSequence s, int start, int count, int after) {/*do nothing*/}

                @Override
                public void onTextChanged(CharSequence s, int start, int before, int count) {/*do nothing*/}

                @Override
                public void afterTextChanged(Editable s) {
                    if(passwordView != null){
                        passwordView.setError(null);
                    }
                }
            });
            builder.setView(dialogView);
            builder.setMessage("Please enter password to finish with action");
            builder.setPositiveButton("Confirm", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    /* do something when click happen, in this case mostly like dummy because data return later
                    * after validation or immediately if required*/
                }
            });
            builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    dialog.cancel();
                }
            });
            builder.setTitle("Confirm password");
        final AlertDialog dialog = builder.create();
        dialog.setOnShowListener(new DialogInterface.OnShowListener() {
            @Override
            public void onShow(final DialogInterface dialogInterface) {
                Button positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
                positiveButton.setOnClickListener(new View.OnClickListener(){

                    @Override
                    public void onClick(View view) {
                        if(passwordView == null || !isAdded()){
                            return;
                        }
                        String password = passwordView.getText().toString();
                        if(PrefUtils.isPasswordValid(getActivity(), password)){
                            if(resultListener == null){
                                return;
                            }
                            /* Return result and dismiss dialog*/
                            resultListener.onValidPassword();
                            dialog.dismiss();
                        } else {
                            /* Show an error if entered password is invalid and keep dialog
                            * shown to the user*/
                            String error = getActivity().getString(R.string.message_password_not_valid);
                            passwordView.setError(error);
                        }
                    }
                });
            }
        });
        return dialog;
    }

    /**
     * Custom callback to return result if entered password is valid
     */
    public static interface OnPaswordCheckResult{
        void onValidPassword();
    }
} 
Xylol answered 23/2, 2015 at 22:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.