I have a dialog with some UI elements in there. This dialog is created and shown at some point later on via show(). I can create the dialog with the default constructor Dialog(Context). But my content view is only set on onCreate which is called after show() function. This causes NPE when I try to modify UI elements like this:
public void showNumber(String number)
{
labelNumber.setText(number);
show();
}
But if call change the above function as below, it works most of the time. (Sometimes it fails if the phone gets slower because setContentView wouldn't be called by the time it executed setText)
public void showNumber(String number)
{
show();
labelNumber.setText(number);
}
How do you create the dialog and set content view without showing it at all. If I call setContentView() manually, it will be re-called when i call show() for the first time.
All you need to do is call create(); on the dialog when you construct it.
When you call show it will create the dialog only if create(); hasn't been called and then call onStart(); on the dialog. Finally it will attach the dialog to the window.
Something like:
Dialog myDialog = new Dialog(context) {
protected void onCreate() {
super.onCreate();
doYourThing
}
};
myDialog.create();
I'm assuming you're doing logic in onCreate, because, in Dialog it's just an empty method for subclasses to override.
onCreate:
http://androidxref.com/5.1.1_r6/xref/frameworks/base/core/java/android/app/Dialog.java#37
show:
http://androidxref.com/5.1.1_r6/xref/frameworks/base/core/java/android/app/Dialog.java#254
Pre API level 21 (if you can't use an AlertDialog.Builder) you should be able to use onRestoreInstanceState to do what you want to do, like this (this is a hack):
Bundle myBundle = new Bundle();
myBundle.putBoolean("android:dialogShowing", false);
myBundle.putBundle("android:dialogHierarchy", new Bundle());
myDialog.onRestoreInstanceState(myBundle);
Info:
http://androidxref.com/4.4_r1/xref/frameworks/base/core/java/android/app/Dialog.java#411
Here is what I would like to do:
1) Inside an Activity a dialog is shown. I use DialogFragment and FragmentManager for this, by calling:
dialogFragment.show(fragmentManager, "edit_task_list");
2) Inside the Dialog I have layout with a custom Button. I would like to perform some action when the button is clicked and later close the dialog.
How should I connect everything? I see two options:
1) onclick attribute in the Button and a method inside the Actvity. That was my original plan, but I don't how to get the Dialog from the Activity to dismiss it. Even if this is not the right way, how could this be done? I would like to understand how this works.
2) set on click listener on the button when the Dialog is created in DialogFragment. This will require me to pass some context from the Activity to the DialogFragment, so I would like to avoid it (and keep the DialogFragment as simple as possible).
Which of those options should I take?
Number 2 Doesn't require you to pass any context (and you shouldn't). You define an interface that can act as a contract between fragments and activities and make your activity implement it.
From your dialog and in your button.onClick(), you do something like this (untested code):
if ( getActivity() != null
&& !getActivity().finishing()
&& getActivity() instanceOf YourInterface) {
((YourInterface)getActivity()).onSomeNiceMethod();
dismiss(); // close the dialog (if this is what you want).
}
The interface looks like:
public interface YourInterface {
void onSomeNiceMethod();
}
And your Activity…
public class YourActivity implements YourInterface {
void onSomeNiceMethod() {
// Hey! The Button In The Dialog Has Been Pressed!
}
}
All Activity and Fragment classes have a built-in callback method for you to use when you start another Activity, Fragment, Dialog, or DialogFragment.
void onActivityResult(int requestCode, int resultCode, Intent data)
Since you want to start the Dialog from an Activity, using the Dialog class is better than the DialogFragment class. The latter is better for starting a dialog from a Fragment, because it has two methods for communicating back to the Fragment (get/set TargetFragment())
The Dialog class has the getOwnerActivity() method. This is the Activity you use when creating the Dialog with one of its constructors.
You set a onClickListener on the button in the Dialog class. To pass the result back to the Activity:
getOwnerActivity().onActivityResult(intIdentifyingme, Activity.RESULT_OK,
intent);
dismiss(); // close the dialog
You put additional info you want to send in an Intent.
1) onclick attribute in the Button and a method inside the Actvity.
That was my original plan, but I don't how to get the Dialog from the
Activity to dismiss it. Even if this is not the right way, how could
this be done? I would like to understand how this works.
Basically your Activity has to remember/know which dialog is active at the moment with something like curDialog=dialogFragment;, then when handling the button onclick action you'll know which dialog to dismiss. But this is really not a good idea since basically the Button View would "leak" from your DialogFragment to your Activity, which breaks object encapsulation.
2) set on click listener on the button when the Dialog is created in
DialogFragment. This will require me to pass some context from the
Activity to the DialogFragment, so I would like to avoid it (and keep
the DialogFragment as simple as possible).
As a previous answer mentioned, you don't need to pass any Context to it, especially since you can get the Activity by calling getActivity().
The solution depends on whether or not this dialog would be used by multiple Activities:
Used by a single Activity: #Martin's solution will work just fine
Used by multiple Activity: abstraction can be used such that only the user's decision is passed to a listener. This is a (modified) solution I came up for the same problem:
public class BaseDialogFragment extends DialogFragment {
protected TextView dialogEn;
protected Button dialogYes;
private Button dialogNo;
protected OnSelectListener listener;
public interface OnSelectListener {
public void onSelect(int type, boolean yes);
}
public void setOnSelectListener(OnSelectListener listener) {
this.listener = listener;
}
public BaseDialogFragment() {
super();
}
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.dialog_confirm, container, false);
dialogYes = (Button) v.findViewById(R.id.yes);
dialogNo = (Button) v.findViewById(R.id.no);
dialogEn = (TextView) view.findViewById(R.id.dialog_en);
dialogEn.setText(getArguments().getString("text_en"));
dialogYes.setOnClickListener(this);
dialogNo.setOnClickListener(this);
return v;
}
public void onClick(View v) {
if (listener != null) {
listener.onSelect(getArguments().getInt("type"),
v == dialogYes ? true : false);
}
getDialog().dismiss();
}
}
To use it some additional info needs to be provided:
Bundle bundle = new Bundle();
bundle.putInt("type", type); //type: an unique integer value that helps differentiate result from different dialogs
bundle.putString("text_en", en); //en: String to be displayed
dialog.setArguments(bundle);
dialog.setOnSelectListener(this);
So if the type value above is set to 115, then a dialogYes button click would trigger public void onSelect(int type, boolean yes) method to be called with 115 and true as the 1st & 2nd parameters.
Your first point about the onClick attribute in the xml should be avoided. Because handling a Dialog that way could be really painfull if you respect events like screen rotation or a setup with multiple dialogs. This leads into leaked window errors most of the time and needs unnecessary code overhead to avoid this. Because you have to keep track of the Dialog which is actually shown yourself.
To be able to dismiss the Dialog this way you can use the Tag you setted as you called dialogFragment.show(fragmentManager, "edit_task_list");
DialogFragment frag = (DialogFragment)getFragmentManager().findFragmentByTag("edit_task_list");
if(frag != null)
frag.dismiss();
The proper solution is to use an interface as a callback for the communication between the DialogFragment and the Activity. This keeps the Dialog modular and the code easy. Here is an example from the docs. For this you don't need a Context. You simply pass the interface to the dialog in the onAttach() callback. It has a reference of the Activity as a parameter, which called that Dialog.
// Example interface for the communication
public interface OnArticleSelectedListener {
public void onButtonClicked(/*any Parameters*/);
}
public static class FragmentA extends DialogFragment {
OnArticleSelectedListener mListener;
...
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mListener = (OnArticleSelectedListener) activity; // get the interface of the Activity
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnArticleSelectedListener");
}
}
...
}
Handle the Button click in the Dialog and call dismiss() in it, that the Dialog can dismiss itself. Have a look at this question why to use dismiss() instead of getDialog().dismiss().
yourButton.setOnClickListener(new OnClickListener(){
#Override
public void onClick(View v){
if(mListener != null) // check if the listener is still valid
mListener.onButtonClicked(...); // calls the Activity implementation of this callback
dismiss(); // dismiss the Dialog
}
});
In onPause() of the Dialog set the reference of the interface to null. This way you can be sure that the callback will only be used if the Dialog is showing.
Your Activity looks something like this to be able to handle the callback:
public class MyActivity extends Activity implements OnArticleSelectedListener{
...
#Override
public void onButtonClicked(...){
// your implementation here
}
}
I don't know your overall setup but if you would use an AlertDialog a click on the Buttons dismiss the Dialog automatically when the method returns.
I'm using Android Support Library (v4) and ActionBarSherlock. I'm trying to close a progress dialog programatically. I've coded a small utility class to help with dialog management.
The dialog is shown from an AsyncTask.onPreExecute. It gets displayed correctly. Then I fire a config change by rotating the device, which destroys the activity (onDestroy calls AsyncTask.cancel(true)). AsyncTask.onCancelled is called, and is in this method where I'm trying to close the dialog. But nothing happens. Here are the helper functions to show and close the dialog:
public abstract class DialogHelperActivity extends SherlockFragmentActivity {
protected void showProgressDialog(final String msg, final String tag){
FragmentTransaction ft = this.getSupportFragmentManager().beginTransaction();
DialogFragment dialogFragment = ProgressDialogFragment.newInstance(msg);
ft.add(dialogFragment, tag);
ft.disallowAddToBackStack();
ft.commitAllowingStateLoss(); //If I try with regular commit(), exceptions are thrown.
}
protected void closeDialog(final String tag){
FragmentManager fm = this.getSupportFragmentManager();
Fragment dialogFragment = fm.findFragmentByTag(tag);
if(dialogFragment != null){
FragmentTransaction ft = fm.beginTransaction();
ft.remove(dialogFragment);
ft.commitAllowingStateLoss();
} else {
System.err.println("dialog not found!"); //This line is hit always
}
}
public static class ProgressDialogFragment extends SherlockDialogFragment {
static ProgressDialogFragment newInstance(final String msg) {
ProgressDialogFragment adf = new ProgressDialogFragment();
Bundle bundle = new Bundle();
bundle.putString("alert-message", msg);
adf.setArguments(bundle);
return adf;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setCancelable(false);
int style = DialogFragment.STYLE_NORMAL, theme = 0;
setStyle(style,theme);
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Bundle bundle = this.getArguments();
String message = bundle.getString("alert-message");
ProgressDialog dialog = new ProgressDialog(getActivity());
if(message != null){
dialog.setMessage(message);
}
dialog.setCancelable(false);
dialog.setIndeterminate(true);
return dialog;
}
}
}
After rotating the device, the AsyncTask is cancelled. I'm calling closeDielog from onPostExecute and also from onCancelled. The dialog never gets closed because the tag ID is not found (findFragmentByTag returns null). I'm puzzled with this. The tag is a static String in my implementation activity so there's no chance of it being lost or changed between the calls to showProgressDialog and closeDialog.
Any idea/hint/suggestion will be much appreciated.
Thanks.
The problem is that I'm cancelling the AsyncTask in the activity's onDestroy. This is ok to get rid of the bg thread, but AsyncTask.onCancelled is no place to close a fragment, because it runs AFTER the activity has been destroyed. Before that, a new activity is created, and the fragment manager restores a new dialog (even if it was created with setRetainInstance(false), which I guess is the default).
The timeline of calls is something like this:
screen rotation triggers a config change
old activity enters onDestroy, cancels the asynctask.
old dialog enters onDetach.
new activity is created.
new dialog is created, gets attached to new activity and is shown.
the old task onCancel executes, calls closeDialog, but the tag is not found.
My error was assuming the string tag identified a fragment globally in the application context, but it turns out that the actual fragment ID assigned by the fragment manager is a combination of fragment tag/id and its activity id. When the activity is destroyed, their fragments are detached, and after this point, even if a new fragment with the same tag/id is in the foreground, as it is attached to a different activity, the fragment manager returns null when the old activity calls findFragmentByTag.
However this tag/id is enough for the new fragment to be passed the arguments bundle of the old fragment. This duality is confusing, but it also enables a hack: We can populate the arguments bundle of the fragment in its onStop callback with a "cancelled" flag, an query about it in the onResume callback, where it calls dismiss itself if the flag is found. That way I can have a progress dialog that conceptually belongs to the AsyncTask, and dies with it.
Im writing an Android application in which a user selection triggers a custom Dialog, from which a selection may trigger a second Dialog.
When showing the initial Dialog from the Activity class, I'm setting an onDismissListener on it to pull out user selections which works fine other in cases where the 2nd Dialog is not triggered. The issue that I'm having is that I can't figure out how to have the first one Dialog remain open until the 2nd one is dismissed, so that the information from both is sent back to the Activity class.
Hopefully some code will make this a little more clear:
MainActivity class where I am launching the initial CustomDialog:
customDialog = new CustomDialog(this);
customDialog.show();
customDialog.setOnDismissListener(new OnDismissListener(){
public void onDismiss(DialogInterface di){
slection = customDialog.getselection();
updateUI(); //updates a listview with the results
}
});
Within the CustumDialog class where I am launching the SecondDialog on top of it:
if(specify){
SecondDialog secondDialog = new SecondDialog(context);
secondDialog.show();
secondDialog.setOnDismissListener( new OnDissmissListener(){
public void onDismiss(DialogInterface di){
// this is where I want to call the CustomDialog's dismiss() method
// so that they both close at the same time and the data from here
// can be sent back to the MainActiivty through the CustomDialog's
// onDismissListener
}
});
}
dismiss();
So, to reiterate: I'm trying to prevent the CustomDialog's dismiss() method to be called until the SecondDialog is also dismissed. Is there a way that I can call it from the SecondDialog's OnDismissListener?
You should create customDialog as an instance level variable. You then it will be accessible with onDismiss(...) of second dialog. There you can call customDialog.dismiss();
// Instance level variable
private Dialog customDialog = null;
Instanciate your customDialog, then create second dialog from within your customDialog. Your Second dialog's code would look like this.
if(specify){
SecondDialog secondDialog = new SecondDialog(context);
secondDialog.show();
secondDialog.setOnDismissListener( new OnDissmissListener(){
public void onDismiss(DialogInterface di){
// customDialog is accessible as it is declared as instance level variable
MyClassName.this.customDialog.dismiss();
}
});
}
dismiss();
I prefer to save the data in 1st dialog before going to send one and when dismiss the 2nd dialog, open the 1st dialog again with saved data .. i used this way in my developing and its effective ..
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.