OnCancelListener is not called in DialogFragment - java

I have a simple AlertDialog that displays a list of some items and upon clicking one of them, the clicked item is passed back to the enclosing Activity. I also want to perform some default handling when the user cancels the dialog (using the back button) - more specifically, I want to pass an empty string to the activity in such case.
However, if I put the dialog in a DialogFragment (from the compatibility package), the OnCancelListener is not called when I close the dialog with the back button. What am I doing wrong?
public class SelectItemDialog extends DialogFragment {
public interface Callback {
void onItemSelected(String string);
}
private static final String ARG_ITEMS = "items";
private Callback callback;
public static SelectItemDialog fromItems(Collection<String> items) {
SelectItemDialog fragment = new SelectItemDialog();
fragment.setArguments(newArguments(items));
return fragment;
}
private static Bundle newArguments(Collection<String> items) {
Bundle arguments = new Bundle();
arguments.putStringArray(ARG_ITEMS, items.toArray(new String[items.size()]));
return arguments;
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
callback = (Callback) activity;
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final String[] items = getArguments().getStringArray(ARG_ITEMS);
return new AlertDialog.Builder(getActivity())
.setTitle(R.string.dialog_select_email_title)
.setItems(items, new OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
callback.onItemSelected(items[which]);
}
})
.setOnCancelListener(new OnCancelListener() {
#Override
public void onCancel(DialogInterface dialog) {
// this code is not executed
callback.onItemSelected("");
throw new RuntimeException("dialog cancelled");
}
})
.create();
}
}

It might have to do with the fact that there is no explicit call to cancel() from your code.
The OnCancelListener documentation says:
This will only be called when the dialog is canceled
Which probably needs an explicit cancel() call.
Either make a positive/negative button with a OnClickListener that calls DialogInterface#cancel() or use a OnDismissListener() with an extra check to see if a list item was clicked.
Also, to listen for a back keypress and cancel the dialog, you can set up an OnKeyListener, like outlined in this SO answer
Also, once you have the Dialog set up, it would also be a good idea to use Dialog#setCanceledOnTouchOutside() in case the the user taps outside the Dialog.
Edit: The below part is the easy way to handle cancel events in a DialogFragment.
Since you are using a DialogFragment, this class has a very handy method, DialogFragment#onCancel() which gets called when the DialogFragment is cancelled. Do your logic in there.
DialogFragments are more complex, with a slightly different lifecycle than normal dialogs. Therefore, first check the documentation if you have a certain Dialog-based approach that you are trying to port to a DialogFragment, some methods may exist that allow your new implementation to function properly!

If you are using DialogFragment and want to listen back button then use this -
this.getDialog().setOnKeyListener(new Dialog.OnKeyListener() {
#Override
public boolean onKey(DialogInterface dialog, int keyCode,
KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
if (****) {
your logic
}
return true;
}
return false;
}
});

Note: DialogFragment own the Dialog.setOnCancelListener and Dialog.setOnDismissListener callbacks. You must not set them yourself.
To find out about these events, override onCancel(DialogInterface) and onDismiss(DialogInterface).
public class SelectItemDialog extends DialogFragment {
#Override
public void onDismiss(DialogInterface dialog) {
super.onDismiss(dialog);
//your code hear
dialog.cancel();
}
}
And you should remove .setOnCancelListener()

Actually if you want to use DialogFragment, you can never add OnCancelListener or OnDismissListener to it, since the Dialog Fragment owns callbacks to these methods!
You have 3 options here:
1- go with regular dialogs.2- set your dialog fragment to cancellable(false) and add a cancel button to the dialog.3- check #Nikhil Pingle answer.
this is from the documentation of the Dialog Fragment
* <p><em>Note: DialogFragment own the {#link Dialog#setOnCancelListener
* Dialog.setOnCancelListener} and {#link Dialog#setOnDismissListener
* Dialog.setOnDismissListener} callbacks. You must not set them yourself.</em>
* To find out about these events, override {#link #onCancel(DialogInterface)}
* and {#link #onDismiss(DialogInterface)}.</p>

Cancel Listener or Dismiss listener in DialogFragment can achieve by onDismiss
DialogFragment newFragment = new DatePickerFragment();
newFragment.show(getFragmentManager(), "datePicker");
newFragment.onDismiss(new DialogInterface(){
#Override
public void cancel() {
// TODO Auto-generated method stub
}
#Override
public void dismiss() {
// TODO Auto-generated method stub
}
});

Related

How to create a util that can be referenced multiple times in different activities

I have successfully implemented a custom Dialog box that appears when the user tries to leave an activity via a back button or by using onBackPressed(). They can simply cancel the dialog box or continue, and leave the activity. This function has been implemented in multiple activities, however its making my code a lot longer than it needs to be. I wanted to know how to create a util that can be referenced in different activities, without the need for the chunk of code to copy pasted multiple times. Please note that I am retrieving the dialog title and description from string.xml
This is my code:
Dialog customDialog;
Button button_one, button_two;
TextView dialog_title, dialog_description;
customDialog = new Dialog(this);
//Back button will close app
#Override
public void onBackPressed() {
customDialog.setContentView(R.layout.custom_dialog_box);
dialog_title = customDialog.findViewById(R.id.dialog_title);
dialog_title.setText(getString(R.string.leaving_activity_warning_title));
dialog_description = customDialog.findViewById(R.id.dialog_description); dialog_description.setText(getString(R.string.leaving_activity_warning_description));
button_one = customDialog.findViewById(R.id.button_one);
button_one.setText(getString(R.string.cancel));
button_two = customDialog.findViewById(R.id.button_two);
button_two.setText(getString(R.string.leave_anyway));
button_one.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
customDialog.dismiss();
}
});
button_two.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
customDialog.dismiss();
finish();
overridePendingTransition(R.anim.slide_in_left, R.anim.slide_out_right);
}
});
Objects.requireNonNull(customDialog.getWindow()).setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
customDialog.show();
}
UPDATE
Created a Java file called "DialogBoxMessage"
DialogBoxMessage Code:
class DialogBoxMessage {
private Dialog customDialog;
private TextView dialog_title, dialog_description;
private Button button_one, button_two;
//Custom Dialog Box Initialization
DialogBoxMessage(Button myButtonOne, TextView myDialogTitle, TextView myDialogDescription, Dialog myCustomDialog) {
customDialog = myCustomDialog;
button_one = myButtonOne;
button_two = myButtonOne;
dialog_title = myDialogTitle;
dialog_description = myDialogDescription;
}
void leaveActivity() {
customDialog.setContentView(R.layout.custom_dialog_box);
dialog_title = customDialog.findViewById(R.id.dialog_title);
dialog_title.setText(Resources.getSystem().getString(R.string.leaving_activity_warning_title));
dialog_description = customDialog.findViewById(R.id.dialog_description);
dialog_description.setText(Resources.getSystem().getString(R.string.leaving_activity_warning_description));
button_one = customDialog.findViewById(R.id.button_one);
button_one.setText(Resources.getSystem().getString(R.string.cancel));
button_two = customDialog.findViewById(R.id.button_two);
button_two.setText(Resources.getSystem().getString(R.string.leave_anyway));
button_one.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
customDialog.dismiss();
}
});
button_two.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
customDialog.dismiss();
}
});
Objects.requireNonNull(customDialog.getWindow()).setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
customDialog.show();
}
}
I input the following code in another activity
Other activity code:
//Reusable exit dialog message
DialogBoxMessage dialogBoxMessage;
//Back button will close app
#Override
public void onBackPressed() {
dialogBoxMessage.leaveActivity();
finish();
}
But it doesn't seem to work, I think there are a lot of issues... please help :(
I assume customDialog is a seperate class you wrote - therefore i would suggest you put main information like contentview, title, message or type in the constructor when you initialize ur Dialog.
For your onClick Method I suggest you create an Interface to handle Button Clicks in your
customDialog class.
This could be implemented as a static method in a utilities class. The method would require 'this' as a parameter, which contains the activity context. The method should return the result of the button press. The activity can use this response to determine if finish() should be called or not.
UPDATE
I had suggested a simple static method, but you've gone down the object-oriented route. That's fine.
However, your constructor requires passing in several views, which wouldn't appear to achieve the code efficiency you are after.
Your constructor should just require the Activity context; everything else is encapsulated in your new class.
In each Activity's onBackPressed method you will need to create the object with
dialogBoxMessage = new DialogBoxMessage(this);
before you can call any of that object's methods.

Returning Data from a Dialog Fragment to the Activity that Called It

Hey fellow stackoverflowers!!!
I'm wondering what the best way to pass a string taken from a Dialog Fragment based on user input on the Dialog into the main activity which called the string?
Here's my specific example but it's really long so if you don't feel like going through it don't worry about everything below.
Here's my source code, I've ommitted the imports n stuff
public class GroupNameFragment extends AppCompatDialogFragment {
private EditText edittGroupName;
public static String GROUP_NAME = "com.example.mashu.walkinggroup.controller - groupName";
// When the views are inflated, get access to them
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
edittGroupName = Objects.requireNonNull(getView()).findViewById(R.id.edittGroupName);
}
#NonNull
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// Get reference to fragment's layout
View view = LayoutInflater.from(getActivity())
.inflate(R.layout.group_name_layout, null);
// OK button listener
DialogInterface.OnClickListener listener = (dialog, which) -> {
if (which == DialogInterface.BUTTON_POSITIVE) {
// If OK pressed, create bundle to be accessed in OnDismissListener in MapActivity,
// which contains the groupName user inputted
String groupName = edittGroupName.getText().toString();
Bundle bundle = new Bundle();
bundle.putString(GROUP_NAME, groupName);
setArguments(bundle);
}
};
// Build alert dialog
return new AlertDialog.Builder(getActivity())
.setTitle("Choose your Group Name!")
.setView(view)
.setPositiveButton(android.R.string.ok, listener)
.create();
}
// Extracts groupName from the bundle set up in the onClickListener above
public static String getGroupName(GroupNameFragment dialog) {
Bundle bundle = getArguments();
return bundle.getString(GROUP_NAME);
}
}
What I attempted to do was to this: First, I get access to the EditText that the user will type in their response. Then I set the Dialog Listener for the OK button which creates a bundle using the setArguments function which contains the groupName when the user is done, which will be accessed in the other activity later on by using the static getGroupName function. Here's the function in the main activity which creates the Dialog and sets the onDismissListener
private void createGroupNameDialog() {
// Instantiate Dialog
// Support Fragment Manager for backwards compatibility
FragmentManager manager = getSupportFragmentManager();
GroupNameFragment dialog = new GroupNameFragment();
dialog.show(manager, "GroupNameDialog");
// OnDismissListener callback function to be run whenever dialog dismissed.
dialog.getDialog().setOnDismissListener(new DialogInterface.OnDismissListener() {
#Override
public void onDismiss(DialogInterface dialogInterface) {
// Update groupName based on what user inputted and update marker name at origin
groupName = GroupNameFragment.getGroupName(dialog);
originMarker.setTitle(groupName);
}
});
}
I think the problem is in groupName = GroupNameFragment.getGroupName(dialog). I feel like theres a better way to get the bundle here, and it seems weird to use the function as static and then pass in specific instance of GroupNameFragment in order to get the bundle (wouldn't that instance be gone by then since it's being used in the "OnDismiss"?). Also, the app crashes the second createGroupNameDialog is called, but it doesn't crash and actually opens the dialog window if I comment out the OnDismissListener, so I'm sure the problems in there somewhere but I don't know why it crashes before the dialog box even opens since OnDismiss happens AFTER the user dismisses the Dialog Box.
Thanks!!!
I accomplished passing variables back using an interface and listeners. I'll show you how I handled it (although I used a DialogFragment, this should still work for AlertDialogs, and in this example I passed an integer, not a string, but it would work for any data type).
public class DialogFragmentOtherMedia extends DialogFragment {
int dialogResult;
//The interface is important!
public interface YesNoListener {
void onYesOtherMedia(int output);
void onNoOtherMedia(int output);
}
//Checking for ClassCastException is nice here.
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (!(activity instanceof YesNoListener)) {
throw new ClassCastException(activity.toString() + " must implement YesNoListener");
}
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
dialogResult = 0;
final String mediaType[] = {getString(R.string.Ringtones),getString(R.string.Music),getString(R.string.Alarms)};
return new AlertDialog.Builder(getActivity())
.setTitle(getString(R.string.Select_Other_Media_Type))
.setSingleChoiceItems(mediaType, dialogResult, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
//Log.d("DialogFragmentOtherMedia.onCreateDialog","Item clicked: " + mediaType[which]);
dialogResult = which;
}
})
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
//Casting the activity to YesNoListener is very important here!
//You'll register the listener in the activity later, by implementing the interface.
((YesNoListener) getActivity()).onYesOtherMedia(dialogResult);
}
})
.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
//Same thing for your other callbacks.
((YesNoListener) getActivity()).onNoOtherMedia(dialogResult);
}
})
.create();
}
}
Then you just need to implement it in your activity where you called the dialog from:
public class AlarmDetailsActivity extends Activity
DialogFragmentOtherMedia.YesNoListener {
//All of your activity stuff here...
#Override
public void onYesOtherMedia(int result) {
Log.i("Tag", "onYes Result: " + result);
}
#Override
public void onNoOtherMedia(int result) {
Log.i("Tag", "onNo Result: " + result);
}
}
Sorry about all of the random strings and extra alert dialog. I just wanted to show some actual working code from my app. I tried to add comments next to the important stuff. Hope this helps!

Refresh adapter in a Fragment after DialogFragment dbflow insert

I have an activity that has 3 fragments on it with Tabs, one of them is called "TaskFragment".
In my main Activity i only load the fragments.
In TaskFragment i have a RecyclerView that is working fine and is showing the items as intended.
The problem comes, when i insert data using a DialogFragment, because it does insert data (i am using DbFlow ORM), but it does not (of course) refresh the adapter since it is in the TaskFragment fragment inside the DetailMainActivity activity as i said.
I have tried to use onResume() and onPause() in order to refresh the adapter, but they are never called since the activity does not get paused or in onresume for a DialogFragment.
I have tried aswell to use an interface, but it does not work and i have searched all over stackoverflow and google with no luck.
I leave here some of my code for you to understand better:
DetailMainActivity.java
Here in the onClick interface i show the DialogFragment to the user to input the information.
FragmentManager fm = getSupportFragmentManager();
AddSimpleTask sptask = new AddSimpleTask();
sptask.show(fm, "tag");
TaskFragment.java
In this fragment i have my RecyclerView
private void setupRecyclerView() {
mRecyclerView.setAdapter(adapter);
mRecyclerView.setLayoutManager(new GridLayoutManager(getActivity(), 2));
mRecyclerView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (DetailMainActivity.FAB_Status) {
DetailMainActivity.hideFAB();
DetailMainActivity.FAB_Status = false;
}
return false;
}
});
}
private void setupAdapter() {
adapter = new DetailMainTaskAdapter(simpleTaskList, this);
}
AddSimpleTask
And this is my DialogFragment. I have set a setOnShowListener() in order to avoid the DialogFragment to get dismiss early.
#Override
public void onShow(DialogInterface dialogInterface) {
final AlertDialog dialog =(AlertDialog) getDialog();
if (dialog != null){
Button positiveButton = dialog.getButton(Dialog.BUTTON_POSITIVE);
Button negativeButton = dialog.getButton(Dialog.BUTTON_NEGATIVE);
positiveButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (mEditTextName.getText().toString().trim().isEmpty() ||
mEditTextContent.getText().toString().trim().isEmpty() ) {
if (mEditTextName.getText().toString().trim().isEmpty()) {
mEditTextName.setError("Can not be empty");
}
if (mEditTextContent.getText().toString().trim().isEmpty()) {
mEditTextContent.setError("Can not be empty");
}
}else {
presenter.beingInsertion(mEditTextName.getText().toString().trim(), mEditTextContent.getText().toString().trim()
, foreignId);
}
}
});
negativeButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
dismiss();
}
});
}
}
If the insert is successfully achieved the onInsertSuccess method is called (i am using MVP)
#Override
public void onInsertSuccess() {
Snackbar.make(getActivity().findViewById(R.id.containerMainDetail), "Actividad agregada", Snackbar.LENGTH_SHORT).show();
dismiss();
}
I have called adapter.notifyDataSetChanged() in many places, and i also tried with a custom interface, but i can not make this work.
Sorry for the long post, but thanks in advance for your help.
There are some errors in your statement but I'll get to that later. notifyDataSetChanged() only notifies the adapter that the underlying list (or array) has changed. The implication is that you first need to requery your database and obtain the new list before calling notifyDataSetChanged() on the adapter else there is no point as the underlying list will still be the same and it will not update the adapter.
The correct way of calling this will be through your custom listener interface and not in the onPause()/onResume() callbacks as there is the possibility that the user does not enter a value and hence you will unnecessarily be querying the database. In your custom listener interface implementation, first update the list with the new data from the DB and then notify the adapter.
Which leads to the error in assumption that onPause()/onResume() callbacks do not happen when your Activity is covered by a DialogFragment - this is incorrect. The moment the activity view is even partially covered, the onPause() callback is triggered.

Difference between removeDialog(), dismissDialog() and dismiss()

What is different between removeDialog() and dismiss() and dismissDialog()? because I'm able to use them together without any problem.
And is it matter when implementing DialogInterface.OnClickListener or AlertDialog.OnClickListener?
I searched a lot but couldn't find anything useful.
EDIT:
I'm developing for Android 2.3.
Example code:
public final class OptionsPreference extends PreferenceActivity implements DialogInterface.OnClickListener
{
private AlertDialog noInternetDialog = null;
//...
#Override
protected void onPause()
{
if (this.noInternetDialog != null)
{
Log.d(LOG_TAG, "Destroying noInternetDialog...");
this.noInternetDialog.dismiss(); // X?
removeDialog(DIALOG_NOINTERNET); // X?
dismissDialog(DIALOG_NOINTERNET); // X?
this.noInternetDialog = null;
}
super.onPause();
}
#Override
protected final Dialog onCreateDialog(final int id)
{
switch (id)
{
case DIALOG_NOINTERNET:
{
final AlertDialog.Builder _builder = new AlertDialog.Builder(this).setIcon(android.R.drawable.ic_dialog_info).setMessage(R.string.str_nointernet);
_builder.setCancelable(false);
_builder.setPositiveButton(R.string.str_wifisettings, this);
_builder.setNeutralButton(R.string.str_ok, this);
this.noInternetDialog = _builder.create();
if (!isFinishing())
{
this.noInternetDialog.show();
}
return this.noInternetDialog;
}
// ...
}
dismissDialog(int id) : Dismisses the dialog with the specified id. It only hides the dialog but still keeps the internal references by the Activity which contains this dialog so that it can be restored in future.Deprecated in API 13.
removeDialog(int id) : It also dismisses the dialog with the specified id. Means it hides that particular dialog and in addition cleans up all the references by the Activity and hence cannot be restored in future. Deprecated in API 13.
dismiss() : This method operates on a particular dialog because it is a method of Dialog class. It also dismisses the dialog. You have to own a valid dialog in order to dismiss it else you'll get exception.

How to trigger an event on Dialog dismiss Android?

I have a custom dialog which extends the Dialog Class, I would like to bind an event to execute some code after the Dialog is closed when the user presses the BACK button of the device. How can I do that? I found a post where someone says that the .addWindowListener() should be used when working with Dialogs and other Window widgets. But the dialog class doesn't have the addWindowListener method, so I cannot use it. Is there another way without using fragments cause I shouldn't re-write the MyCustomDialog class?
This is the code:
public class MyCustomDialog extends Dialog {
public MyCustomDialog(Context context, int layoutResourceId) {
super(context);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(layoutResourceId);
}
}
Thanks for the attention!
EDIT: i found this on the android developers site, is there a way to use it with MyCustomDialog class?
onDismiss DialogInterface
Since you are extending android Dialog class you can implement a Dismiss Listener in your Activity's and set it when you create the Dialog, and then in the listener implement any functionality you want depending on the button that was used to dismiss the Dialog.
Hope this will solve your problem.
Edit You can use dialog.setCanceledOnTouchOutside(false); which will stop closing the dialog if you touch outside of the dialog.
Something like,
Dialog dialog = new Dialog(context)
dialog.setCanceledOnTouchOutside(false);
OR Alternatively
Override onTouchEvent() of dialog and check for action type. if the action type is
'MotionEvent.ACTION_OUTSIDE' means, user is interacting outside the dialog region. So in this case, you can dimiss your dialog or decide what you wanted to perform.
view plainprint?
dialog.setOnTouchListener(new View.OnTouchListener(){
#Override
public boolean onTouch(View v, MotionEvent event)
{
if(event.getAction() == MotionEvent.ACTION_OUTSIDE){
Toast.make(getApplicationContext(), "TOuched outside the dialog", Toast.LENGTH_LONG).show();
this.dismiss();
}
return false;
}
});
And for back press you can do dialog.setCancelable(false); which will prevent dialog getting cancelled from backpress event.
OR you can alternatively override setOnKeyListener event and put your own code into it.
Edit
dialog.setOnKeyListener(new Dialog.OnKeyListener() {
#Override
public boolean onKey(DialogInterface arg0, int keyCode,
KeyEvent event) {
// TODO Auto-generated method stub
if (keyCode == KeyEvent.KEYCODE_BACK) {
finish();
dialog.dismiss();
}
return true;
}
});
Happy Coding!!!
You need to Override onBackPressed inside Dialog class. Also make sure to close dialog after override OnBackPressed .
Try this
public class MyCustomDialog extends Dialog {
public MyCustomDialog(Context context, int layoutResourceId) {
super(context);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(layoutResourceId);
}
#Override
public void onBackPressed() {
// TODO Auto-generated method stub
super.onBackPressed();
dismiss(); // make sure to call dismiss to close dialog
// put your code here
}
}
If you want to trigger an event if user clicked/touched outside the dialog and close it or used the back button to close then use
dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
#Override
public void onCancel(DialogInterface dialog) {
//your trigger goes here
Toast.makeText(IntroductoryActivity.this, "on cancel", Toast.LENGTH_SHORT).show();
}
});
But if you want to trigger an event if something dismisses the dialog, like some other event then use
dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
#Override
public void onDismiss(DialogInterface dialog) {
//your trigger goes here
Toast.makeText(IntroductoryActivity.this, "on dismiss", Toast.LENGTH_SHORT).show();
}
});

Categories

Resources