I'm testing this on an Emulator with the Android O Developer Preview. On previous versions everything works fine.
I got a LoginFragment which shows a "Please Wait" progress dialog when the login is processing.
public class ProgressDialogHud extends DialogFragment {
private String messages;
public static ProgressDialogHud newInstance(String message) {
ProgressDialogHud dialog = new ProgressDialogHud();
// ...
return Dialog;
}
}
public class LoginFragment extends Fragment {
private DialogFragment mProgressDialog;
private void login() {
mProgressDialog = ProgressDialogHud.newInstance( "..." );
mProgressDialog.show( getActivity().getSupportFragmentManager(), "PROGRESS" );
}
private void onLoginFinished() {
mProgressDialog.dismiss(); // NullPointerException here because inside Fragment (DialogFragment extends Fragment) the FragmentManager is null
}
}
I did a litle debugging session and found that the DialogFragment uses 2 different FragmentManager for showing and hiding. On showing the fragment, the manager is not null, however it's null when it's hiding.
Here is the Stacktrace
Any ideas?
You were close to the answer already: good investigation.
Use getFragmentManager() instead of getActivity().getSupportFragmentManager() when showing the dialog.
Related
I know that Google's Material Design guidelines don't recommend using a ProgressDialog, instead using another less intrusive way to display progress, but I need to use a ProgressDialog for a specific Activity of my app.
So, the thing is that I want to wrap a ProgressDialog inside a DialogFragment, and thus my code is as follows:
public class MaterialProgressDialogFragment extends DialogFragment {
private int total;
private MaterialDialog myDialog;
#NonNull
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return myDialog;
}
public void incrementProgress(int by) {
myDialog.incrementProgress(by);
}
public void setTotal(int total) {
this.total = total;
}
public void setUp(Context context) {
myDialog = new MaterialDialog.Builder(context)
.title("Progress")
.content("Processing...")
.progress(false, total, true)
.build();
}
}
Because what I want to build is a determinate ProgressDialog, I want to be able to update its progress throghout the life of my app. For this I have made a method called setProgress(progress), but myDialog is always null, as well as the value returned from getDialog().
What am I doing wrong?
Thank you
EDIT: I'm showing the dialog inside my fragment's onCreateActivity() method, like follows:
MaterialProgressDialogFragment dialogFragment = new MaterialProgressDialogFragment();
dialogFragment.setTotal(100);
dialogFragment.setUp(getActivity());
dialogFragment.show(getSupportFragmentManager(), "");
dialog.incrementProgress(50);
Everything works as expected until the last line, which causes the app to throw an exception.
It isn't totally clear what the variable dialog inside setProgress(int progress) method is. But if you mean getDialog() by that, it takes time to create dialog by dialog fragment, and between the dialogFragment.show(getSupportFragmentManager(), "") and onCreateDialog(Bundle savedInstanceState) callback there will be some time interval, during which getDialog() will return null.
EDIT:
Ok, now it is more clear. But by the code above, you're violating the fragment framework rules. You should create your dialog in onCreateDialog(Bundle savedInstanceState) method, because otherwise you'll have problems with lifecycle (for example, if you'll rotate your screen, the app will crash).
I suggest you to use something like that:
public class MaterialProgressDialogFragment extends DialogFragment {
public static final String TOTAL_KEY = "total";
public static ProgressDialogFragment newInstance(int total) {
Bundle args = new Bundle();
args.putInt(TOTAL_KEY, total);
ProgressDialogFragment pdf = new ProgressDialogFragment();
pdf.setArguments(args);
return pdf;
}
#NonNull
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
MaterialDialog myDialog = new MaterialDialog.Builder(context)
.title("Progress")
.content("Processing...")
.progress(false, getTotal(), true)
.build();
return myDialog;
}
public void incrementProgress(int by) {
if (getDialog()!=null)
((MaterialDialog)getDialog()).incrementProgress(by);
}
public int getTotal() {
return getArguments().getInt(TOTAL_KEY);
}
}
You should save total variable to arguments, because it will be destroyed on configuration change (for example screen rotation).
Then just create and show it by:
MaterialProgressDialogFragment dialogFragment = MaterialProgressDialogFragment.newInstance(100);
dialogFragment.show(getSupportFragmentManager(), "");
When you want to change your progress, call:
dialog.incrementProgress(50);
But remember, dialog won't be created immediately, so if you'll call this right after show(), it won't take effect, because getDialog() will return null. If you want to just test it, call it delayed:
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
dialog.incrementProgress(50);
}
}, 200);
But anyway in real app you will change your progress from some background process.
What I want to do
I have a dialog fragment shown from Activity A which contains a button. When the button is pressed Activity B is started. Within activity B, what ever action the user does causes activity B to end via a call to finish() however I wish to dismiss the Dialog fragment from Activity B before the call to finish() so that when the user navigates back to activity A, this dialog is no longer shown.
The problem
As you see in the code section, I have recreated the problem on a small scale to avoid posting huge amounts of code and to simplify the question. The issue is that the application I have contains a lot of activities which can show the dialog fragment so I do not know how to keep track of the activity which started the dialog fragment. However I know that another activity cannot find fragments added from another so I was wondering if there is someone who knows a way around this problem.
The code
Dialog Fragment
public class MyDialogFragment extends DialogFragment {
public static final String TAG = "MyDialogFragment";
public static MyDialogFragment newInstance(){
MyDialogFragment myDialogFragment = new MyDialogFragment();
return myDialogFragment;
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.mydialogfragment,container,false);
Button goToSomeOtherActivity = (Button)view.findViewById(R.id.goToSomeOtherActivity);
goToSomeOtherActivity.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent intent = new Intent(getActivity(),SomeOtherActivity.class);
startActivity(intent);
}
});
return view;
}
Activity A - which displays the dialog fragment on button press
public void showDialogFragment(View view)
{
MyDialogFragment.newInstance().show(getSupportFragmentManager(),MyDialogFragment.TAG);
}
Activity B - This one attempts to close the dialog from activity A via a button click
public void dismissDialogFragment(View view)
{
MyDialogFragment myDialogFragment = (MyDialogFragment) getSupportFragmentManager().findFragmentByTag(MyDialogFragment.TAG);
if(myDialogFragment != null)
{
Log.d("SomeOtherActivity","Its not null, we got it");
}
else{
Log.d("SomeOtherActivity","it was null :(");
}
}
What I have tired
Below is what I have tried to do which did not work. I had the idea that if a global fragment manager was used to show the fragment and this manager should be able to find it.
What I did was create a base activity which Activity A and B extend from.
public class BaseActivity extends ActionBarActivity {
private FragmentManager globalFragmentManager;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
globalFragmentManager = getSupportFragmentManager();
}
#Override
protected void onDestroy() {
super.onDestroy();
}
#Override
protected void onPause() {
super.onPause();
}
public FragmentManager getGlobalFragmentManager(){
return globalFragmentManager;
}
}
Now activity A and B used this to show and attempt to dismiss the dialog fragment
In Activity A
MyDialogFragment.newInstance().show(getGlobalFragmentManager(),MyDialogFragment.TAG);
In activity B
public void dismissDialogFragment(View view){
MyDialogFragment myDialogFragment = (MyDialogFragment)getGlobalFragmentManager().findFragmentByTag(MyDialogFragment.TAG);
if(myDialogFragment != null)
{
Log.d("SomeOtherActivity","Its not null, we got it");
}
else{
Log.d("SomeOtherActivity","it was null :(");
}
}
This still resulted in the dialog being null and not able to dismiss.
As far i have understood your problem the major issue is you start a dialog in Activity A and then move to Activity B, and when you are coming back to A your dialog is still present if that's the scenario then do this in Activity A
#override
protected void onPause(){
//write code to dismiss dialog
}
whenever new Activity is started the old one goes to onPause()(if not finished).
This has been driving me nuts and I cannot find an answer anywhere. A very simple spinner dialog, but the setMessage is not working, it's blank!
public class MainActivity extends FragmentActivity {
ProgressDialog loadingProgress;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
loadingProgress = new ProgressDialog(this);
loadingProgress.setIndeterminate(true);
loadingProgress.setMessage("Loading");
loadingProgress.setProgressStyle(ProgressDialog.STYLE_SPINNER);
}
In AsyncTask I just show the ProgressDialog:
protected void onPreExecute() {
super.onPreExecute();
loadingProgress.show();
}
The result is this:
Blank, nothing... Doesn't matter if it's simulator or device... Any ideas why? Your help is appreciated.
I've tested your code and saw the "Loading" string is displayed very well.
Seeing that your screenshot has the space of textview, i think it will be the problem of text color or theme.
If setMessage works, you can detect in "Dump View Hierarchy for UI Automator" tool of device tab in Eclipse like following screenshot.
Here is the code I am using in my app. It works well -- a spinner with a message. The main differences between what you show and this are: 1) I'm using a DialogFragment; 2) I don't call setProgressStyle. Not sure which (if either) of these matter, but this code definitely is working for me.
public void showProgressDialog(int stringResId, boolean isCancelable) {
Bundle arguments = new Bundle();
arguments.putString(EXTRA_MESSAGE, getString(stringResId));
arguments.putBoolean(EXTRA_CANCELABLE, isCancelable);
DialogFragment fragment = new ProgressDialogFragment();
fragment.setArguments(arguments);
showDialog(fragment);
}
public static class ProgressDialogFragment extends DialogFragment {
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Bundle arguments = getArguments();
String message = arguments.getString(EXTRA_MESSAGE, null);
boolean isCancelable = arguments.getBoolean(EXTRA_CANCELABLE, true);
ProgressDialog dialog = new ProgressDialog(getActivity());
dialog.setIndeterminate(true);
if (message != null) {
dialog.setMessage(message);
}
dialog.setCancelable(isCancelable);
dialog.setCanceledOnTouchOutside(isCancelable);
return dialog;
}
}
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.
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
}