DialogFragment Orientation Change Crash with getActivity() - android

I'm currently having some problems with DialogFragments.
I'm using the latest v.4 support packages (Revision 8 I believe)
My problem is, that if the orientation of my phone changes while the dialog is open, the application begins acting wierd.
Currently my application works like this:
There is a FragmentActivity, it calls a Fragment.
This Fragment then calls a DialogFragment (via getActivity().getSupportFragmentManager().
If the orientation changes while the dialog is open, the getActivity() in the Fragment = null.
This causes a problem if I want to finish the Activity etc.
To cause this you open the dialog, change the orientation and press a button. Only after you press the button it crashes
My DialogFragment is called AlertDialogFragment:
public class AlertDialogFragment extends DialogFragment {
private static Builder mBuilder;
private static DialogInterface.OnClickListener mListener;
public static AlertDialogFragment newInstance(Context context, DialogInterface.OnClickListener listener) {
mBuilder = new AlertDialog.Builder(context);
mListener = listener;
return new AlertDialogFragment();
}
//... some functions to set Icons etc
public void setButton(int whichButton, CharSequence buttonText) {
final DialogInterface.OnClickListener listener = mListener == null ? null : mListener;
mBuilder.setPositiveButton(buttonText, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
listener.onClick(dialog, whichButton);
}
});
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return mBuilder.create();
}
}
This is the Fragment:
public class TestBedFragment extends Fragment implements DialogInterface.OnClickListener {
// onCreateView Stuff
private void showCrashDialog() {
FragmentTransaction ft = getActivity().getSupportFragmentManager().beginTransaction();
AlertDialogFragment newDialog = AlertDialogFragment.newInstance(getActivity(), this);
newDialog.setTitle("Test");
newDialog.setIcon(android.R.drawable.ic_dialog_alert);
newDialog.setMessage("Testing testing testing... 1, 2, 3... Just press Ok.");
newDialog.setButton(DialogInterface.BUTTON_POSITIVE, "Ok");
newDialog.show(ft, "dialog");
// Cause the problem. Simulate the user turning the screen
getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
#Override
public void onClick(DialogInterface dialog, int which) {
/*
* hexnumber = a hex number
* Normally equals: TestBedFragment{hexnumber #0 id=hexnumber}
* With crash equals: TestBedFragment{hexnumber}
*/
Log.e("TestBedFragment", "this = " + this);
/*
* hexnumber = a hex number
* Normally equals: com.example.TestBed.TestBedActivity#hexnumber
* With crash equals: null
*/
Log.e("TestBedFragment", "getActivity() = " + getActivity()); // Will equal null... ???
getActivity().finish();
}
}
I'm not too sure whats causing this? Sorry if its a stupid question. I have read in other places about 'Windows Leaking', yet I haven't seen any mention of that stuff in logcat.
Can anyone help me :) Its greatly appreciated
Thanks

You have to understand Activity life cycle. Everytime you rotate the device, the activity is recreated. It means a new instance of the class is executing.
What happens in your code is that, after a rotation, the listener calls getActivity on a previous dialog instance, referring to the previous activity. But this activity is not valid any more, it has been "destroyed" after the rotation and a new one appeared.
You call to getActivity is not valid anymore.
Can't you close your open dialog during fragment onDestroy method ?

You can dismiss your dialog in onPause() and use a conditional to check and show() the dialog in onCreate(). This may be more appropriate in onStop() or onDestroy() depending on your specific use-case.
#Override
protected void onPause() {
if (myDialog != null && myDialog.isVisible())
myDialog .dismiss();
super.onPause();
}

You are recreating everything instead of using the bundle. Add an if to your onCreateDialog to check for a null bundle. If it is null, do your creation stuff, otherwise do nothing and Android will restore everything from the bundle.
I think that will fix your issue.
if(SavesIinstanceState == null); {
// your code
}

Related

Can getActivity() be null, if called inside a button listener of an AlertDialog which is instantiated inside a Fragment?

I need to finish some Activity if "OK" button of an AlertDialog is clicked. The Activity is just a container for another Fragment; And AlertDialog is built inside the Fragment. Something like this:
public class F extends Fragment {
...
public void confirmExit() {
new AlertDialog.Builder(getContext())
...
.setPositiveButton("OK", (dialogInterface, i) -> getActivity().finish())
...
.show();
}
...
}
Now, the AndroidStudio shows me this warning:
Method invocation 'finish' may produce 'java.lang.NullPointerException'
So it thinks that getActivity() may return null. But is this even possible? If the Dialog is on the screen and is interactable, can the underlying Activity be null? If not how to suppress this warning without adding boilerplate code?
Yes.
It may crash because you call getActivity() on an interface and not on the Fragment.
You need to store an instance of your Activity outside the DialogInterface and you will be able to pass the instance on the DialogInterface.
Should be something like that.
public class F extends Fragment {
private Context mContext;
...
#Override
void onAttach(Context context) {
mContext = context
}
public void confirmExit() {
new AlertDialog.Builder(getContext())
...
.setPositiveButton("OK", (dialogInterface, i) -> ((YourParentActivity) mContext).finish())
...
.show();
}
...
}

Second dialog is automatically destroyed when created from within a first dialog's OnClickListener

Basically, I have a login screen where users type their email and password to log in. After they have submitted their data, I check if they have confirmed their email address. If not, I display a dialog with a corresponding message. In that dialog I provide them with a neutral button to request a new confirmation email, if they haven't received one yet. If they clicked on that button, I wanna show another dialog with a message that the email has been successfully sent. The problem is that whenever I create and show the second dialog from within the first dialog's OnClickListener, the second dialog is instantiated, but then destroyed immediately. So my question is, why is this happening and how do I implement this kind of functionality so that whatever fragment is being shown will be retained across rotation?
NotVerifiedEmailDialog.java (first dialog):
public class NotVerifiedEmailDialog extends DialogFragment
{
private static final String TAG = "NotVerifiedEmailDialog";
#NonNull
#Override
public Dialog onCreateDialog(Bundle savedInstanceState)
{
return new AlertDialog.Builder(getActivity())
.setTitle(R.string.email_not_verified)
.setMessage(R.string.email_not_verified_message)
.setPositiveButton(android.R.string.ok, null)
.setNeutralButton(R.string.request_new_email, new DialogInterface.OnClickListener()
{
#Override
public void onClick(DialogInterface dialogInterface, int which)
{
EmailSentDialog dialog = new EmailSentDialog();
dialog.show(getChildFragmentManager(), dialog.getMyTag());
}
})
.create();
}
public String getMyTag()
{
return TAG;
}
}
EmailSentDialog.java (second dialog):
public class EmailSentDialog extends DialogFragment
{
private static final String TAG = "EmailSentDialog";
#NonNull
#Override
public Dialog onCreateDialog(Bundle savedInstanceState)
{
return new AlertDialog.Builder(getActivity())
.setTitle(R.string.success)
.setMessage(R.string.email_sent_message)
.setPositiveButton(android.R.string.ok, null)
.create();
}
public String getMyTag()
{
return TAG;
}
}
Try making the following variable as instance variable. Your dialog object may get destroyed by the previous dialog.
EmailSentDialog dialog;
In case anyone encountered this problem, the solution is to replace getChildFragmentManager() with getParentFragment().getChildFragmentManager(). The former gets a child fragment manager of the first dialog, which is gonna be destroyed, because of the button click (that's why the second dialog is destroyed instantly, because it is tied to the first dialog's child fragment manager), while the latter gets a child fragment manager of the parent fragment (in my case, LoginFragment) and, therefore, is not destroyed immediately.

Android DialogFragment crash on screen rotation

I have a DialogFragment class. I have to set the listener every time it shown (It has multiple cases in my app).
But when I rotate the screen mListener becomes null and there is a NullPointerExcpetion when I click a button. I can't implement the listener in the activity because it has a few cases for this dialog, each has different action.
The CustomDialog class:
MyDialogListener mListener;
public void show(FragmentManager fm, MyDialogListener listener) {
mListener = listener;
super.show(fm, "MyDialog");
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
.setTitle("Title")
.setPositiveButton(android.R.string.ok, new OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int whichButton) {
mListener.onDialogPositiveClick();
// NullPointerException after a screen rotate
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
}
The activity class:
public void showMyFirstDialog() {
new CutsomDialog().show(getFragmentManager(), mFirstListener);
}
public void showMySecondDialog() {
new CutsomDialog().show(getFragmentManager(), mSecondListener);
}
You cannot preserve instance fields of a Fragment (including a DialogFragment). The mechanism for having local data survive configuration changes is to set the fragment's arguments to a Bundle that contains your data; this bundle will survive configuration changes.
First, eliminate the show() method; it's not the correct approach. Instead, you can do something like this:
DialogFragment frag = new MyDialogFragment();
Bundle args = new Bundle();
args.putString("TITLE", "Dialog Title Goes Here");
args.putString("MESSAGE", "This is a dialog messaage");
frag.setArguments(args);
frag.show();
Then you can retrieve the title and message when you create the AlertDialog:
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Bundle args = getArguments();
String title = args.getString("TITLE");
String message = args.getString("MESSAGE");
// set up and return the alert dialog as before
}
Dealing with the DialogListener is a little more complex. You don't want to be holding a reference to that across config changes because it will lead back to the destroyed activity. Instead, you can arrange to retrieve the listener from the activity inside the fragment's onAttach() method:
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// now cast activity to your activity class and get a reference
// to the listener
}
You may need to change your activity class(es) a bit to get this to work right. If you're using this dialog fragment from many activities, it's particularly helpful here to define an interface that the activities can implement to request a listener. It would then look something like this:
public interface DialogListenerProvider {
DialogListener getDialogListener();
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (activity instanceof DialogListenerProvider) {
mListener = ((DialogListenerProvider) activity).getDialogListener();
} else {
// throw an error
}
}
The listener should not be passed in as an argument but instead implemented as part of interface both within the dialogfragment itself and may be an activity. That way, when the positive / negative click happens, you can update data on something and pass it to listener. The listener, when implemented by activity, would pass on the data to teh activity and you can take corresponding action in activity then.
Check these few examples -
http://www.i-programmer.info/programming/android/7426-android-adventures-custom-dialogs-using-dialogfragment.html?start=2
http://android-developers.blogspot.com/2012/05/using-dialogfragments.html
Hope it helps.

Update fragment on dialog fragment option selection

I have fragment that on a component click pop-ups DialogFragment. This dialog fragment holds list of options. When an option from list is selected I want to notify fragment so I can run fields update procedure.
I did something like this
#Override
public void onClick(DialogInterface dialog, int item) {
updateSharedPreference(item);
Log.e("ProfilePersonaListDialog", "Click on dialog, inside onClick");
OnCloseListDialogListener act = (OnCloseListDialogListener) getActivity();
act.onDialogListSelection();
dismiss();
}
However this getActivity() calls on FragmentActivity and not the fragment that triggered the dialog fragment.
I could kill currently open/running fragment and call a new instance that would get updated fields, but that is dirty solution that I would prefer to avoid.
Any suggestions how to go about this update of fragment once option selected in dialog fragment?.
Just coming back with solution. My problem was actually forwarding current fragment getTag() string as parameter of show() for DialogFragment. If anyone interested here is working sample.
Create simple listener
public interface OnCloseListDialogListener {
public void onDialogListSelection();
}
Create new dialog that will extend DialogFragment
public class ListDialogFragment extends DialogFragment implements DialogInterface.OnClickListener {
private PersonaData[] mPersonaData;
private String[] mPersonaName;
private final String TAG;
public static ListDialogFragment newInstance(PersonaData[] personaData, String tag) {
ListDialogFragment dialog = new ListDialogFragment(personaData, tag);
Bundle bundle = new Bundle();
dialog.setArguments(bundle);
return dialog;
}
private ListDialogFragment(PersonaData[] personaData, String tag) {
this.mPersonaData = personaData.clone();
this.TAG = tag;
}
#Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
setCancelable(true);
int style = DialogFragment.STYLE_NORMAL, theme = 0;
setStyle(style, theme);
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(R.string.dialog_title);
mPersonaName = getData();//Your own implementation here
builder.setNegativeButton("Cancel", this);
builder.setSingleChoiceItems(mPersonaName, -1, new SingleChoiceListener());
return builder.create();
}
#Override
public void onClick(DialogInterface dialogInterface, int i) {
}
private class SingleChoiceListener implements DialogInterface.OnClickListener {
#Override
public void onClick(DialogInterface dialog, int item) {
updateSharedPreference(item);
OnCloseListDialogListener act = (OnCloseListDialogListener) getFragmentManager().findFragmentByTag(TAG);
act.onDialogListSelection();
dismiss();
}
}
}
And then in fragment from which you wish to call this dialog do as bellow. DIALOG is just String constant I put there just dialog
SOME_CLICKABLE.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
FragmentManager manager = getFragmentManager();
ListDialogFragment dialog = ListDialogFragment.newInstance(mPersona, getTag());
dialog.show(manager, DIALOG);
}
});
It is necessary in most cases that a Fragment be aware that it is running under the context of an Activity of some description and acceptable for the child Fragment to invoke a method on an interface implicitly implemented by the parent Activity (as demonstrated by the cast in your code snippet). When you get your references working as Tomasz points out you'll be golden.
However, :) to aid the re-usability of the dialog fragment I would suggest that you leverage BroadcastReceivers. A BroadcastReceiver simply broadcasts a message saying I did 'x'. The parent activity or in indeed any other top level component can then declare I am listening for 'x'. Once, the event has been fired in the dialog component, this event will be collected by the parent Activity's onReceive where you can run the necessary code to update your fields.
On a personal level, I prefer this loose coupling over the casting interface approach since it forces me to think about the purpose of each Fragment and keep it modular.
If you want to give it a shot then have a read over the dev guide section on BroadcastReceivers and follow the follow steps;
Implement the BroadcastReceiver in your parent activity. Notice an onReceive method is required to be implemented.
Override the parent Activity's onResume method and register the the activity as a receiver of an event with intent action "blah". Something like;
#Override
protected void onResume() {
super.onResume();
registerReceiver(this, new IntentFilter("blah"));
}
Override the parent Activity's onPause method an unregister the activity as the receiver so as to avoid 'leaked receivers' (you'll find out).
#Override
protected void onPause() {
super.onPause();
unregisterReceiver(deleteSpotReceiver);
}
In your DialogFragment onClick fire the event which your parent activity is 'listening' for.
#Override
public void onClick(DialogInterface dialog, int item) {
updateSharedPreference(item);
Log.e("ProfilePersonaListDialog", "Click on dialog, inside onClick");
final Intent intent = new Intent();
intent.setAction("blah");
getActivity().sendBroadcast(intent);
dismiss();
}
The parent activity will collect the message and you can continue processing. Let me know if you decide to adopt that method.
Just the way you did it above and add sth like that in your activity :
public void onDialogListSelection() {
AnotherFragment anotherFragment = (AnotherFragment) getSupportFragmentManager()
.findFragmentById(R.id.anotherFragment);
anotherFragment.customMethodToNotifyListHasBeenSelected();
}
Of course, if you are not use Support Library then call getFragmentManager instead of getSupportFragmentManager.

Prevent dialog dismissal on screen rotation in Android

I am trying to prevent dialogs built with Alert builder from being dismissed when the Activity is restarted.
If I overload the onConfigurationChanged method I can successfully do this and reset the layout to correct orientation but I lose sticky text feature of edittext. So in solving the dialog problem I have created this edittext problem.
If I save the strings from the edittext and reassign them in the onCofiguration change they still seem to default to initial value not what was entered before rotation. Even if I force an invalidate does seem to update them.
I really need to solve either the dialog problem or the edittext problem.
Thanks for the help.
The best way to avoid this problem nowadays is by using a DialogFragment.
Create a new class which extends DialogFragment. Override onCreateDialog and return your old Dialog or an AlertDialog.
Then you can show it with DialogFragment.show(fragmentManager, tag).
Here's an example with a Listener:
public class MyDialogFragment extends DialogFragment {
public interface YesNoListener {
void onYes();
void onNo();
}
#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) {
return new AlertDialog.Builder(getActivity())
.setTitle(R.string.dialog_my_title)
.setMessage(R.string.dialog_my_message)
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
((YesNoListener) getActivity()).onYes();
}
})
.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
((YesNoListener) getActivity()).onNo();
}
})
.create();
}
}
And in the Activity you call:
new MyDialogFragment().show(getSupportFragmentManager(), "tag"); // or getFragmentManager() in API 11+
This answer helps explain these other three questions (and their answers):
Android Best way of avoid Dialogs to dismiss after a device rotation
Android DialogFragment vs Dialog
How can I show a DialogFragment using compatibility package?
// Prevent dialog dismiss when orientation changes
private static void doKeepDialog(Dialog dialog){
WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
lp.copyFrom(dialog.getWindow().getAttributes());
lp.width = WindowManager.LayoutParams.WRAP_CONTENT;
lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
dialog.getWindow().setAttributes(lp);
}
public static void doLogout(final Context context){
final AlertDialog dialog = new AlertDialog.Builder(context)
.setIcon(android.R.drawable.ic_dialog_alert)
.setTitle(R.string.titlelogout)
.setMessage(R.string.logoutconfirm)
.setPositiveButton("Yes", new DialogInterface.OnClickListener()
{
#Override
public void onClick(DialogInterface dialog, int which) {
...
}
})
.setNegativeButton("No", null)
.show();
doKeepDialog(dialog);
}
If you're changing the layout on orientation change I wouldn't put android:configChanges="orientation" in your manifest because you're recreating the views anyway.
Save the current state of your activity (like text entered, shown dialog, data displayed etc.) using these methods:
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
}
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
}
That way the activity goes through onCreate again and afterwards calls the onRestoreInstanceState method where you can set your EditText value again.
If you want to store more complex Objects you can use
#Override
public Object onRetainNonConfigurationInstance() {
}
Here you can store any object and in onCreate you just have to call getLastNonConfigurationInstance(); to get the Object.
Just add android:configChanges="orientation" with your activity
element in AndroidManifest.xml
Example:
<activity
android:name=".YourActivity"
android:configChanges="orientation"
android:label="#string/app_name"></activity>
A very easy approach is to create the dialogs from the method onCreateDialog() (see note below). You show them through showDialog(). This way, Android handles the rotation for you and you do not have to call dismiss() in onPause() to avoid a WindowLeak and then you neither have to restore the dialog. From the docs:
Show a dialog managed by this activity. A call to onCreateDialog(int, Bundle) will be made with the same id the first time this is called for a given id. From thereafter, the dialog will be automatically saved and restored.
See Android docs showDialog() for more info. Hope it helps somebody!
Note: If using AlertDialog.Builder, do not call show() from onCreateDialog(), call create() instead. If using ProgressDialog, just create the object, set the parameters you need and return it. In conclusion, show() inside onCreateDialog() causes problems, just create de Dialog instance and return it. This should work! (I have experienced issues using showDialog() from onCreate() -actually not showing the dialog-, but if you use it in onResume() or in a listener callback it works well).
This question was answered a long time ago.
Yet this is non-hacky and simple solution I use for myself.
I did this helper class for myself, so you can use it in your application too.
Usage is:
PersistentDialogFragment.newInstance(
getBaseContext(),
RC_REQUEST_CODE,
R.string.message_text,
R.string.positive_btn_text,
R.string.negative_btn_text)
.show(getSupportFragmentManager(), PersistentDialogFragment.TAG);
Or
PersistentDialogFragment.newInstance(
getBaseContext(),
RC_EXPLAIN_LOCATION,
"Dialog title",
"Dialog Message",
"Positive Button",
"Negative Button",
false)
.show(getSupportFragmentManager(), PersistentDialogFragment.TAG);
public class ExampleActivity extends Activity implements PersistentDialogListener{
#Override
void onDialogPositiveClicked(int requestCode) {
switch(requestCode) {
case RC_REQUEST_CODE:
break;
}
}
#Override
void onDialogNegativeClicked(int requestCode) {
switch(requestCode) {
case RC_REQUEST_CODE:
break;
}
}
}
Definitely, the best approach is by using DialogFragment.
Here is mine solution of wrapper class that helps to prevent different dialogs from being dismissed within one Fragment (or Activity with small refactoring). Also, it helps to avoid massive code refactoring if for some reasons there are a lot of AlertDialogs scattered among the code with slight differences between them in terms of actions, appearance or something else.
public class DialogWrapper extends DialogFragment {
private static final String ARG_DIALOG_ID = "ARG_DIALOG_ID";
private int mDialogId;
/**
* Display dialog fragment.
* #param invoker The fragment which will serve as {#link AlertDialog} alert dialog provider
* #param dialogId The ID of dialog that should be shown
*/
public static <T extends Fragment & DialogProvider> void show(T invoker, int dialogId) {
Bundle args = new Bundle();
args.putInt(ARG_DIALOG_ID, dialogId);
DialogWrapper dialogWrapper = new DialogWrapper();
dialogWrapper.setArguments(args);
dialogWrapper.setTargetFragment(invoker, 0);
dialogWrapper.show(invoker.getActivity().getSupportFragmentManager(), null);
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDialogId = getArguments().getInt(ARG_DIALOG_ID);
}
#NonNull
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return getDialogProvider().getDialog(mDialogId);
}
private DialogProvider getDialogProvider() {
return (DialogProvider) getTargetFragment();
}
public interface DialogProvider {
Dialog getDialog(int dialogId);
}
}
When it comes to Activity you can invoke getContext() inside onCreateDialog(), cast it to the DialogProvider interface and request a specific dialog by mDialogId. All logic to dealing with a target fragment should be deleted.
Usage from fragment:
public class MainFragment extends Fragment implements DialogWrapper.DialogProvider {
private static final int ID_CONFIRMATION_DIALOG = 0;
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
Button btnHello = (Button) view.findViewById(R.id.btnConfirm);
btnHello.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
DialogWrapper.show(MainFragment.this, ID_CONFIRMATION_DIALOG);
}
});
}
#Override
public Dialog getDialog(int dialogId) {
switch (dialogId) {
case ID_CONFIRMATION_DIALOG:
return createConfirmationDialog(); //Your AlertDialog
default:
throw new IllegalArgumentException("Unknown dialog id: " + dialogId);
}
}
}
You can read the complete article on my blog How to prevent Dialog being dismissed? and play with the source code.
It seems that this is still an issue, even when "doing everything right" and using DialogFragment etc.
There is a thread on Google Issue Tracker which claims that it is due to an old dismiss message being left in the message queue. The provided workaround is quite simple:
#Override
public void onDestroyView() {
/* Bugfix: https://issuetracker.google.com/issues/36929400 */
if (getDialog() != null && getRetainInstance())
getDialog().setDismissMessage(null);
super.onDestroyView();
}
Incredible that this is still needed 7 years after that issue was first reported.
You can combine the Dialog's onSave/onRestore methods with the Activity's onSave/onRestore methods to keep the state of the Dialog.
Note: This method works for those "simple" Dialogs, such as displaying an alert message. It won't reproduce the contents of a WebView embedded in a Dialog. If you really want to prevent a complex dialog from dismissal during rotation, try Chung IW's method.
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
myDialog.onRestoreInstanceState(savedInstanceState.getBundle("DIALOG"));
// Put your codes to retrieve the EditText contents and
// assign them to the EditText here.
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// Put your codes to save the EditText contents and put them
// to the outState Bundle here.
outState.putBundle("DIALOG", myDialog.onSaveInstanceState());
}
I had a similar problem: when the screen orientation changed, the dialog's onDismiss listener was called even though the user didn't dismiss the dialog. I was able to work around this by instead using the onCancel listener, which triggered both when the user pressed the back button and when the user touched outside of the dialog.
In case nothing helps, and you need a solution that works, you can go on the safe side, and each time you open a dialog save its basic info to the activity ViewModel (and remove it from this list when you dismiss dialog). This basic info could be dialog type and some id (the information you need in order to open this dialog). This ViewModel is not destroyed during changes of Activity lifecycle. Let's say user opens a dialog to leave a reference to a restaurant. So dialog type would be LeaveReferenceDialog and the id would be the restaurant id. When opening this dialog, you save this information in an Object that you can call DialogInfo, and add this object to the ViewModel of the Activity. This information will allow you to reopen the dialog when the activity onResume() is being called:
// On resume in Activity
override fun onResume() {
super.onResume()
// Restore dialogs that were open before activity went to background
restoreDialogs()
}
Which calls:
fun restoreDialogs() {
mainActivityViewModel.setIsRestoringDialogs(true) // lock list in view model
for (dialogInfo in mainActivityViewModel.openDialogs)
openDialog(dialogInfo)
mainActivityViewModel.setIsRestoringDialogs(false) // open lock
}
When IsRestoringDialogs in ViewModel is set to true, dialog info will not be added to the list in view model, and it's important because we're now restoring dialogs which are already in that list. Otherwise, changing the list while using it would cause an exception. So:
// Create new dialog
override fun openLeaveReferenceDialog(restaurantId: String) {
var dialog = LeaveReferenceDialog()
// Add id to dialog in bundle
val bundle = Bundle()
bundle.putString(Constants.RESTAURANT_ID, restaurantId)
dialog.arguments = bundle
dialog.show(supportFragmentManager, "")
// Add dialog info to list of open dialogs
addOpenDialogInfo(DialogInfo(LEAVE_REFERENCE_DIALOG, restaurantId))
}
Then remove dialog info when dismissing it:
// Dismiss dialog
override fun dismissLeaveReferenceDialog(Dialog dialog, id: String) {
if (dialog?.isAdded()){
dialog.dismiss()
mainActivityViewModel.removeOpenDialog(LEAVE_REFERENCE_DIALOG, id)
}
}
And in the ViewModel of the Activity:
fun addOpenDialogInfo(dialogInfo: DialogInfo){
if (!isRestoringDialogs){
val dialogWasInList = removeOpenDialog(dialogInfo.type, dialogInfo.id)
openDialogs.add(dialogInfo)
}
}
fun removeOpenDialog(type: Int, id: String) {
if (!isRestoringDialogs)
for (dialogInfo in openDialogs)
if (dialogInfo.type == type && dialogInfo.id == id)
openDialogs.remove(dialogInfo)
}
You actually reopen all the dialogs that were open before, in the same order. But how do they retain their information? Each dialog has a ViewModel of its own, which is also not destroyed during the activity lifecycle. So when you open the dialog, you get the ViewModel and init the UI using this ViewModel of the dialog as always.
Yes, I agree with the solution of using DialogFragment given by #Brais Gabin, just want to suggest some changes to the solution given by him.
While defining our custom class that extends DialogFragment, we require some interfaces to manage the actions ultimately by the activity or the fragment that has invoked the dialog. But setting these listener interfaces in the onAttach(Context context) method may sometimes cause ClassCastException that may crash the app.
So to avoid this exception, we can create a method to set the listener interfaces and call just it after creating the object of the dialog fragment.
Here is a sample code that could help you understand more-
AlertRetryDialog.class
public class AlertRetryDialog extends DialogFragment {
public interface Listener{
void onRetry();
}
Listener listener;
public void setListener(Listener listener)
{
this.listener=listener;
}
#NonNull
#Override
public Dialog onCreateDialog(#Nullable Bundle savedInstanceState) {
AlertDialog.Builder builder=new AlertDialog.Builder(getActivity());
builder.setMessage("Please Check Your Network Connection").setPositiveButton("Retry", new
DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
//Screen rotation will cause the listener to be null
//Always do a null check of your interface listener before calling its method
if(listener!=null&&listener instanceof HomeFragment)
listener.onRetry();
}
}).setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
return builder.create();
}
}
And in the Activity or in the Fragment you call-
AlertRetryDialog alertRetryDialog = new AlertRetryDialog();
alertRetryDialog.setListener(HomeFragment.this);
alertRetryDialog.show(getFragmentManager(), "tag");
And implement the methods of your listener interface in your Activity or the Fragment-
public class YourActivity or YourFragment implements AlertRetryDialog.Listener{
//here's my listener interface's method
#Override
public void onRetry()
{
//your code for action
}
}
Always make sure that you do a null check of the listener interfaces before calling any of its methods to prevent NullPointerException (Screen rotation will cause the listener interfaces to be null).
Please do let me know if you find this answer helpful. Thank You.
Just use
ConfigurationChanges = Android.Content.PM.ConfigChanges.Orientation | Android.Content.PM.ConfigChanges.ScreenSize
and app will know how to handle rotation and screen size.

Categories

Resources