This seems like it should be simple, but I'm not finding an answer anywhere. I have an Android application that performs network tasks in the background. If an error comes back, I want to display an error dialog. When the task returns, I don't know which Activity is in the foreground. Based on this post, it looks like we can't use the application context to display a dialog (and indeed I do get the crash if I try).
So how can I get the context of the current activity? Again, the receiver for the network task is running in the Application context, not in a particular Activity. Any other ideas?
Edit: I should clarify. I don't want to display an error dialog if I'm not the foreground application. I'm only interested in the case where our app is in the foreground for now.
If an error comes back, I want to display an error dialog.
Please only do this if you know that the user is actively using your application. The user will be very very annoyed if you interrupt them in the middle of something else (playing a game, watching a movie, reading a book).
So how can I get the context of the current activity?
You don't. At most, you let the current activity know that it needs to do something.
Any other ideas?
One possibility is to use an ordered broadcast, so if you have a foreground activity, it gets control, otherwise you raise a Notification to let the user know about the problem without popping a dialog. The activity that receives the ordered broadcast can display an AlertDialog or otherwise let the user know about the problem. I wrote about the details of how to do this in a blog post (and a book chapter, for that matter), and here is a sample application demonstrating the technique.
Or, have the service call startActivity() to start up a dialog-themed activity.
I've created a helper class that implements CommonsWare's idea. Activities that wish to display alerts just need to call Alerts.register() and Alerts.unregister(). Then anyone can call Alerts.displayError().
Comments welcome.
public class Alerts {
private static class AlertReceiver extends BroadcastReceiver {
private static HashMap<Activity, AlertReceiver> registrations;
private Context activityContext;
static {
registrations = new HashMap<Activity, AlertReceiver>();
}
static void register(Activity activity) {
AlertReceiver receiver = new AlertReceiver(activity);
activity.registerReceiver(receiver, new IntentFilter(MyApplication.INTENT_DISPLAYERROR));
registrations.put(activity, receiver);
}
static void unregister(Activity activity) {
AlertReceiver receiver = registrations.get(activity);
if(receiver != null) {
activity.unregisterReceiver(receiver);
registrations.remove(activity);
}
}
private AlertReceiver(Activity activity) {
activityContext = activity;
}
#Override
public void onReceive(Context context, Intent intent) {
abortBroadcast();
String msg = intent.getStringExtra(Intent.EXTRA_TEXT);
displayErrorInternal(activityContext, msg);
}
}
public static void register(Activity activity) {
AlertReceiver.register(activity);
}
public static void unregister(Activity activity) {
AlertReceiver.unregister(activity);
}
public static void displayError(Context context, String msg) {
Intent intent = new Intent(MyApplication.INTENT_DISPLAYERROR);
intent.putExtra(Intent.EXTRA_TEXT, msg);
context.sendOrderedBroadcast(intent, null);
}
private static void displayErrorInternal(Context context, String msg) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle("Error")
.setMessage(msg)
.setCancelable(false)
.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
});
final AlertDialog alert = builder.create();
alert.show();
}
}
While this question is quite old, meanwhile there is a nice solution if you want it to work with any active Activity. So you wouldn't need to register individual Activities like it's proposed in other answers.
I want to emphasize that this is usually something that you should try to avoid - creating dialogs w/o knowing the view context, but there might be special circumstances where this can be useful.
With this solution using ActivityLifecycleCallbacks you're always aware of the active Activity on Application level. You can then use this active Activity to open dialogs or other Activities from e.g. network code where you would only have access to the Application class:
class YourApplication : Application() {
private val activeActivityCallbacks = ActiveActivityLifecycleCallbacks()
override fun onCreate() {
super.onCreate()
registerActivityLifecycleCallbacks(activeActivityCallbacks)
}
override fun onTerminate() {
unregisterActivityLifecycleCallbacks(activeActivityCallbacks)
super.onTerminate()
}
fun getActiveActivity(): Activity? = activeActivityCallbacks.getActiveActivity()
// ...
}
class ActiveActivityLifecycleCallbacks : Application.ActivityLifecycleCallbacks {
private var activeActivity: Activity? = null
fun getActiveActivity(): Activity? = activeActivity
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
activeActivity = activity
}
override fun onActivityDestroyed(activity: Activity) {
if (activity === activeActivity) {
activeActivity = null
}
}
// ...
}
From anywhere:
YourApplication.get().getActiveActivity()?.let { activity ->
activity.runOnUiThread {
AlertDialog.Builder(activity).setMessage("test").show()
}
}
Please check other SO posts how to implement a getter for the Application: e.g. Kotlin singleton application class
I'm using custom created Dialog. It will be called by Service or Handler even in the case if the current Activity will lose focus.
Activity of my custom Dialog:
public class AlertDialogue extends AppCompatActivity {
Button btnOk;
TextView textDialog;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
supportRequestWindowFeature(Window.FEATURE_NO_TITLE); //comment this line if you need to show Title.
setContentView(R.layout.activity_alert_dialogue);
textDialog = (TextView)findViewById(R.id.text_dialog) ;
textDialog.setText("Hello, I'm the dialog text!");
btnOk = (Button) findViewById(R.id.button_dialog);
btnOk.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
finish();
}
});
}
}
You can call this dialog using:
Intent intent = new Intent(this, AlertDialogue.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
In the Manifest:
<activity android:name=".AlertDialogue"
android:theme="#style/AlertDialogMy">
</activity>
style:
<resources>
<style name="AlertDialogMy" parent="Theme.AppCompat.Light.Dialog">
<item name="android:windowNoTitle">true</item> //delete this line if you need to show Title.
</style>
</resources>
Here is the full code of this example.
Why don't use event bus (https://github.com/greenrobot/EventBus) ?
Define events:
public class ShowErrorMessageEvent {
public String errorMessage;
public ShowErrorMessageEvent(String errorMessage) {
this.errorMessage = errorMessage;
}
public String getErrorMessage() {
return this.errorMessage;
}
}
Prepare subscribers for all activities you needed:
#Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
#Override
public void onStop() {
EventBus.getDefault().unregister(this);
super.onStop();
}
In all activities, show the dialog if the event received
public void onEvent(ShowErrorMessageEvent event) {
/* Show dialog with event.getErrorMessage() from background thread */
};
In your background thread, post the error event:
EventBus.getDefault().post(new ShowErrorMessageEvent("This is an error message!"));
Here is an implementation that will allow an AlertDialog to be displayed on top of the current active activity (this is an example of a message dialog, but can be used for alerts as well).
public class AlertsDialogue
{
private AlertDialog.Builder alertDialogBuilder;
private AlertDialog alert;
public AlertsDialogue(Context context, String title, String message)
{
alertDialogBuilder = new AlertDialog.Builder(context);
alertDialogBuilder.setTitle(title);
alertDialogBuilder.setIcon(R.drawable.ic_launcher);
alertDialogBuilder.setMessage(message)
.setCancelable(false)
.setPositiveButton(context.getString(R.string.text_ok), new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which)
{
alert.dismiss();
}
});
alert = alertDialogBuilder.create();
Window window = alert.getWindow();
if (window != null)
{
// the important stuff..
window.setType(WindowManager.LayoutParams.TYPE_TOAST);
alert.show();
}
else
Toast.makeText(context, message, Toast.LENGTH_LONG).show();
}
}
The dialog will be displayed even if the context on which it was instantiated with is no longer active, much like Toast.
Call with new AlertsDialogue(MyActivity.this, "title", "message");
No additional permissions are required in the AndroidManifest file.
What type of network tasks are going on in the background. I would suggest possibly rethinking the design. Perhaps a notification would be better? Or maybe a "results summary" screen. As a user I Would rather a non obtrusive signal of error if I am not actively waiting for the task to complete.
I also face the problem. I find out a simple and effective solution. Usuallay, we have a base activity used to handle some common logic. So have this:
public class BaseActionBarActivity extends ActionBarActivity{ //this BaseActionBarActivity is the Base Act
private static BaseActionBarActivity current;
#Override
protected void onStart() {
super.onStart();
current=this;
}
public static BaseActionBarActivity getCurrentContext() {
return current;
}
}
current field is the current Context Actitvity. And I believe there is not memory leak question. It works well for me! Hope helpful
you need to reset type of Window which dialog attached, as follow:
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
do not forget declaring "android.permission.SYSTEM_ALERT_WINDOW" permission in your manifest.
The best thing is to show dialogs and other view related work in Activity / Fragment, not in some other class that has no idea what context is.
But there are some valid reasons to want to do that outside of Views. For example, I want to show some alert dialog to help testing the app in mock build. To provide activity context you can do several things, I will write about two:
Inject activity context with scope tied to activity. It is the best solution, but requires to have architecture that is correctly implemented, with scoping in mind. I will not describe how to do it here, it can be found in tutorials.
I have created some helper class with activity's extention to get context from activity. It doesn't store context, but function that can provide context, so I think it shouldn't cause memory leaks (? not tested, it is for mocked builds for testers, so didn't spend much time checking that)
object ActivityContextProviderForMockBuilds {
private val contextProvider = MutableSharedFlow<(Context) -> Unit>(extraBufferCapacity = 1)
fun getContext(contextCallback: (Context) -> Unit) = contextProvider.tryEmit(contextCallback)
fun AppCompatActivity.setContextProviderForMockBuild() {
if (BuildConfig.MOCKS_ENABLED) {
contextProvider.onEach {
it.invoke(this)
}.launchIn(lifecycleScope)
}
}
}
Usage:
ActivityContextProviderForMockBuilds.getContext { context -> ... }
But as said previously, you shouldn't use activity context outside of views in most cases and if yes, then by dependency injection :)
Well, I think that the best for you would be an AsyncTask. Here an example and doc:
http://developer.android.com/reference/android/os/AsyncTask.html
Related
I need to clear password field on exit of app,i am exiting the app in other activity and on exit it goes to mainActivity which has login details in which i need to clear password field,how will i do this in other activity ,i tried using setText("") but in vain.
public void backButtonHandler() {
AlertDialog.Builder alertDialog = new AlertDialog.Builder(
ReminderActivity.this);
// Setting Dialog Title
alertDialog.setTitle("Leave application?");
// Setting Dialog Message
alertDialog.setMessage("Are you sure you want to leave the application?");
// Setting Positive "Yes" Button
alertDialog.setPositiveButton("YES",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
//I need to clear here all pwd data present in MainActivity in edittext
finish();
}
});
// Setting Negative "NO" Button
alertDialog.setNegativeButton("NO",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// Write your code here to invoke NO event
dialog.cancel();
}
});
// Showing Alert Message
alertDialog.show();
}
In your MainActivity do like this:
#Override
public void onResume() {
super.onResume(); // Always call the superclass method first
editText.SetText("");
}
So, it will clear editText value, whenever your MainActivity will get resumed.
Or when you click the login button do
editText.SetText("");
Then it will be cleared before another activity starts
Using setText won't work and may even throw some type of NPE if your activity is no longer in memory.
There are a couple of ways you can do this depending on your requirements:
You can simply clear the text field in the activity/fragment's onStop or onDestroy overrides
If you absolutely have to set the text field from a different activity, then I suggest the following design:
In your Application class, create a nested class that implements ActivityLifeCycleCallbacks. This is a great class to implement as it allows you to monitor the state of all of your activities. It resolved a lot of problems for me
If you have a singleton model, or static String value that binds to your password TextView then you're almost there (other workarounds available if you give us more info).
So let's look at some code:
Design 1:
public void onStop()
{
super.onStop();
myTextField.setText("");
}
Your Application class:
public class MyApplication extends Application
{
...
private static final class MyActivityLifeCycleCallback implements ActivityLifecycleCallbacks
{
#Override
public void onActivityStopped(Activity activity)
{
//Here, I am assuming that your class name is MainActivity
Log.i(TAG, "onActivityStopped:" + activity.getLocalClassName());
if("MainActivity".isEqual(activity.getLocalClassName())
{
myDataModel.getInstance().setPassword("");
//or if your password String member is static
//((MainActivity)activity).myPasswordMember = "";
}
//Or if you want to only clear the password text when RAActivity stops, simply replace "MainActivity" with the RAActivity class name.
}
#Override
public void onActivityDestroyed(Activity activity,
Bundle savedInstanceState)
{
Log.i(TAG, "onActivityDestroyed:" + activity.getLocalClassName());
if(activity.getLocalClassName().contains("MySecondActivity"))
{
//reset your password here - implementation will depend on how you have your data model organized (private, singleton, static, etc.)
}
}
....
}
Remember that this design would work well with some type of a singleton or central data model (think MVC architecture) so that you can propagate the change in data to your components.
Hope it helps
EDIT:
I have added the code according to your comment. But to be honest, I think it's a better idea to simply call the setText("") method in the MainActivity's onStop function like I suggested. This is a simple problem and my second design might be a bit too much. Anyway, the code is updated, so if you like it, mark it as an answer :)
Here's another idea. IF RAActivity calls MainActivity again (probably not) using startActivity, you can simply pass a bundle value to MainActivity that tells it that it's coming from RAActivity. That way, MainActivity can clear the password if it was called from RAActivity. Lots of options.
It's pretty common for my app to show a progress or AlertDialog to the user. If the user puts the app into the background and then returns later, I want the Dialog to still be shown. Is there a way to make Android handle this? I'd like it to either not close the dialog, or if it does reopen it automatically when the Activity resumes.
So far it's looking like no. I haven't found a ton of results about this (most people run into issues with orientation change, which my app does not allow) but very few ask about going into the background. I have tried every permutation of DialogFragment and regular Dialog, but they all disappear when the home button is pressed and the app is opened from the task manager.
I don't even have any code to show because it's all in the testing phase of various examples online. I suspect I will have to manage this myself, by checking in onResume() if something should be shown. If this is the case I can live with it, but I'd like to know for sure.
First lets clear something, like you can see in the next images, your activity or fragment can be destroyed for many reasons, so you have to deal with what you want saving "the state of your dialog".
Now the code:
public class CustomProgressDialog extends Dialog {
private static final String SHOWING_PROGRESS_DIALOG = "showing_progress_dialog";
private static final String STRING_PROGRESS_DIALOG = "string_progress_dialog";
private static final String SHOWING_POP_UP_DIALOG = "showing_pop_up_dialog";
private static final String STRING_POP_UP_DIALOG = "string_pop_up_dialog";
public TextView textView;
public CustomProgressDialog(Context context) {
super(context, android.R.style.Theme_Translucent_NoTitleBar);
setContentView(R.layout.progress_layout);
setCancelable(false);
textView = (TextView) findViewById(R.id.progress_textView);
}
}
public class MasterActivity extends FragmentActivity {
private CustomProgressDialog progressDialog;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_master);
progressDialog = new CustomProgressDialog(this);
if (savedInstanceState != null) {
boolean showingDialog = savedInstanceState.getBoolean(SHOWING_PROGRESS_DIALOG);
if (showingDialog) {
String msg = savedInstanceState.getString(STRING_PROGRESS_DIALOG, getResources().getString(R.string.progress_default_text));
progressDialog.textView.setText(msg);
progressDialog.show();
}
boolean mShowing_PopUpdialog = savedInstanceState.getBoolean(SHOWING_POP_UP_DIALOG);
String temp_msg = savedInstanceState.getString(STRING_POP_UP_DIALOG, "");
if (mShowing_PopUpdialog)
showPopUpDialog(temp_msg);
}
}
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (progressDialog.isShowing()) {
outState.putBoolean(SHOWING_PROGRESS_DIALOG, true);
outState.putString(STRING_PROGRESS_DIALOG, progressDialog.textView.getText().toString());
}
if (alert != null && alert.isShowing()) {
outState.putBoolean(SHOWING_POP_UP_DIALOG, true);
outState.putString(STRING_POP_UP_DIALOG, mString_dialog);
}
}
}
Try this in your DialogFragment's onPause() do this
#Override
public void onPause() {
super.onPause();
getActivity().getSupportFragmentManager().beginTransaction()
.addToBackStack("mydialogfragment").commit();
Log.v("dialog", "dialog is going down");
}
Then in your Activity's onResume() you call the DialogFragment back to life
#Override
protected void onResume() {
super.onResume();
getSupportFragmentManager().popBackStack();
Log.v("activity", "onresume called - i am bringing back the dialog");
}
Why i think this might work is a DialogFragment is a Fragment, and a Fragment's lifecycle is controlled by the Parent Activity as user #0mach0 diagram shows, so all you do is you push it do the backstack and call it back. so it should work
Try showing the dialog using parentFragmentManager. Worked for my DialogFragment
.show(parentFragmentManager, "TAG")
I am displaying a pop up from Notification of my application.But when I check my notification no activity is running from my application so in notification when I show dialogue. Dialogue throws exception because no activity is running and context which I am passing to dialogue constuctor is null therefore I need a context when no activity is running thanks in advance. Or any Idea how I can implement this thanks in advance
you can make your dialog extend and Activity class and change its theme to dialog in the manifest file. in this case, you call notification.setLatestEventInfo(YourActivityDialog.this, contentTitle, contentText, contentIntent);.
I suffered with the same issue. I can't able to use the spinner in my activity. I solved this by using the setContentView like as following
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
View viewToLoad = LayoutInflater.from(this.getParent()).inflate(R.layout.main, null);
this.setContentView(viewToLoad);
}
Another way you can use the separate activity for you dialog. By setting activity like follows
style.xml
<resources>
<style name="AppTheme" parent="android:Theme.Dialog">
<item name="android:windowNoTitle">true</item>
</style>
</resources>
In AndroidManifest Declare activity like this
<activity
android:name="DialogActivity"
android:theme="#style/AppTheme" />
#Override
protected Dialog onCreateDialog(int i)
{
LayoutInflater factory = LayoutInflater.from(this);
final View textEntryView = factory.inflate(R.layout.sendemail, null);
final View textEntryView1 = factory.inflate(R.layout.sendemail_title, null);
to_id = (EditText) textEntryView.findViewById(R.id.edit_text_sender_mail_id);
from_id =(EditText) textEntryView.findViewById(R.id.id);
pwd =(EditText) textEntryView.findViewById(R.id.pwd);
return new AlertDialog.Builder(current_activity.this)
.setTitle("hello")
.setCustomTitle(textEntryView1)
.setView(textEntryView)
.setPositiveButton("Send", new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int whichButton)
{
// code here...
}
})
.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton)
{
System.exit(0);
}
})
.create();
}
You may create MyApplication class, inherited from the Application, then inside that class do the following:
public class MyApplication extends Application {
private static MyApplication instance;
#Override
public void onCreate() {
super.onCreate();
instance = this;
.........
}
public static Context getContext() {
return instance;
}
After that, you may use MyApplication.getContext() anywhere if you need a context and don't have an Activity lying around.
I would assume that your code is running in a background in an Android Service. If that's the case, your Service is a Context itself, so you can use it when you need one.
Of your code is running in a background thread and not in an Android Service (in which case you have other big problems), you could use an instance of the application context. You get one though calling Context.getApplicationContext, and you can get it in the Activity that starts your background code and cache it for later.
Note though that there are certain gotchas with using application context, instead of activity context - for example, you can't use LayoutInflater from application context.
I am trying to create an application with a widget. When the user places the widget on the desktop a listview should come up with a list of items. The user selects an item then the widget is created with the respective text related to that item. I thought I should do this by showing a dialog in the Service but it throws me
Caused by: android.view.WindowManager$BadTokenException: Unable to add
window -- token null is not for an application
to the dialog_newitem.show(); line. For simplicity I am using now a simple alertdialog.
Is it the way to do this? I haven't found anyhing about this on the net.
public class UpdateWidgetService extends Service {
private static final String LOG = "de.vogella.android.widget.example";
public static String ACTION_WIDGET_CONFIGURE = "ConfigureWidget";
public static String ACTION_WIDGET_RECEIVER = "ActionReceiverWidget";
String value;
Dialog dialog_newitem;
EditText et_newitem;
#Override
public void onStart(Intent intent, int startId) {
Toast.makeText(this, "UpdateWidgetService", Toast.LENGTH_SHORT).show();
dialog_newitem = new Dialog(this); //I tried UpdateWidgetService.this, too
dialog_newitem.setContentView(R.layout.dialog_productlists_grp_capitalized);
dialog_newitem.setTitle("Select");
dialog_newitem.setCancelable(true);
et_newitem = (EditText) dialog_newitem.findViewById(R.id.et_item_name);
Button btn_Save = (Button) dialog_newitem.findViewById(R.id.btn_save_pr);
btn_Save.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
value = et_newitem.getText().toString();
}
});
Button btn_Cancel = (Button) dialog_newitem.findViewById(R.id.btn_cancel_pr);
btn_Cancel.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
dialog_newitem.dismiss();
}
});
dialog_newitem.show(); //error
Toast.makeText(this, "value: " + value, Toast.LENGTH_SHORT).show();
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
}
I have used this alertdialog in some other part of the code, and there it is working fine. I think it has something to do with the service.
You can't show a dialog in the service.
if you really want to show a dialog.
try to start an Activity and set the Activity's Theme to Theme.Dialog.
There is a demo in The ApiDemo Project
I know this thread is old, but thought it would be worth contributing anyway for future sufferers.
Although most will say its not recommended to launch dialogs directly from a service, the following workaround works for me. Use the ServiceDialogBuilder class below to build your AlertDialog. Unlike the AlertDialog.Builder, this will work with a Service context and show() can be called directly from a service without having to start a new activity.
Just be wary that this is a bit of a hack, so there may well be some unintended side effects from doing this.
Hope this helps
public class ServiceDialogBuilder extends AlertDialog.Builder {
public ServiceDialogBuilder(Context context) {
super(context);}
#Override
public AlertDialog create() {
AlertDialog dialog=super.create();
//Change dialog window type from TYPE_CHANGED to TYPE_SYSTEM_ALERT
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
return dialog;
}
#Override
public AlertDialog show() {
return super.show();
}}
Just make sure your dialog's window is set to SYSTEM_ALERT:
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
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.