I have an app with a single activity and two fragments. Fragment B is added(addedToBackStack) on Top of Fragment A. In Fragment B, I am showing a dialog, going back to Fragment A and then dismissing the dialog. If getActivity()!=null check is removed inside the handler, the code works fine. But getActivity() is null inside handler. Why is getActivity() null inside handler in the following piece of code?
private void showDialog(final Dialog dialog) {
dialog.show();
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
if (getActivity()!=null && !getActivity().isFinishing()) {
dialog.dismiss();
}
}
}, 1000);
if (getActivity() != null && !getActivity().isFinishing())
getActivity().onBackPressed();
}
getActivity() is null because after getActivity().onBackPressed(); call your fragment will get detached from the Activity. So inside handler it will be always null because its getting called with a delay.
If you want to dismiss dialog after one second and also move back to previous fragment then you should move onBackPressed inside the run method. i have replaced the null check with isAdded().
private void showDialog(final Dialog dialog) {
dialog.show();
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
if (isAdded()) {
dialog.dismiss();
getActivity().onBackPressed();
}
}
}, 1000);
}
Related
I'm trying to perform an action in one fragment, then move to the previous fragment and show a snackbar with a message, confirming the action from the first fragment. However, I'm creating and showing the snackbar in the first fragment (the one I'm moving from), and the snackbar does not appear in the fragment I'm changing to, probably because it's shown in the fragment I'm moving from.
I'm executing the code inside an alertdialog:
builder.setPositiveButton(positiveText, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
dbHandler.deleteExercise(exercise.getId());
// Making the snackbar here did not work, either.
getFragmentManager().popBackStack();
Snackbar snack = Snackbar.make(mainLayout, "Exercise deleted", Snackbar.LENGTH_SHORT);
snack.show();
}
});
Any idea how I could go about achieving this?
Thanks!
EDIT:
I made this incredibly crude drawing of the flow to make it clearer what I'm trying to achieve.
I ended up implementing messaging between Fragments via the main Activity and checking for messages in the fragment's onResume() method:
MainActivity:
private String fragmentTransactionMessage;
#Override
protected void onCreate(Bundle savedInstanceState) {
// Snip
// Initialize fragment transaction message
fragmentTransactionMessage = null;
// Snip
}
// Three methods used to send information (text) between fragments
public void setFragmentTransactionMessage(String message) {
this.fragmentTransactionMessage = message;
}
public String getFragmentTransactionMessage() {
return fragmentTransactionMessage;
}
public void resetFragmentTransactionMessage() {
this.fragmentTransactionMessage = null;
}
Fragment 2:
// Remove exercise from database
dbHandler.deleteExercise(exercise.getId());
// Update message in main activity
((MainActivity)getActivity()).setFragmentTransactionMessage("Item deleted");
// Move to previous fragment
getFragmentManager().popBackStack();
Fragment 1:
#Override
public void onResume() {
super.onResume();
// Check for messages in main activity
String message = ((MainActivity)getActivity()).getFragmentTransactionMessage();
// If any, display as snackbar
if(message != null) {
Snackbar snack = Snackbar.make(mainLayout, message, Snackbar.LENGTH_SHORT);
snack.show();
// Reset message in activity
((MainActivity)getActivity()).resetFragmentTransactionMessage();
}
}
Another suggestion is to wrap getFragmentManager().popBackStack(); in a method which takes a Runnable Obj which will run after a chosen delay
For Example:
public void goBack(Runnable runAfterBack, long delay) {
mFragmentActivity.onBackPressed();
if (runAfterBack != null) {
(new Handler(Looper.getMainLooper())).postDelayed(runAfterBack, delay);
}
}
Usage:
goBack(new Runnable() {
#Override
public void run() {
Toast.makeText(mActivity, "STRING", Toast.LENGTH_SHORT).show();}
}, 1500);
mSomeFragment = new SomeFragment();
mSomeFragment.show(getFragmentManager(), "some");
The Fragment shows fine.
mSomeFragment = new SomeFragment();
mSomeFragment.show(getFragmentManager(), "some");
mSomeFragment.onDismiss(new DialogInterface() {
#Override
public void cancel() {
//
}
#Override
public void dismiss() {
//
}
});
But when I set onDismiss, this doesn't work (the Fragment doesn't shows). I wanna do some operations when the dialog dismisses.
Could you tell me why??
Calling onDismiss actually calls this code
if (mDialog != null) {
mDialog.dismiss();
mDialog = null;
}
which dismisses the dialog. If you want to listen for events on the dialog use onOptionsItemSelected()
I have a dialog fragment with a simple indeterminate progress bar in the centre, which i use to show network activity:
public class NativeLoadingDialogFragment extends DialogFragment {
public NativeLoadingDialogFragment() {
// Blank
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Dialog dialog = new Dialog(getActivity(), android.R.style.Theme_Dialog);
ProgressBar indeterminateProgressBar = new ProgressBar(getActivity());
indeterminateProgressBar.setIndeterminate(true);
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
dialog.setCancelable(false);
dialog.setCanceledOnTouchOutside(false);
dialog.setContentView(indeterminateProgressBar);
dialog.getWindow().setBackgroundDrawable(
new ColorDrawable(android.graphics.Color.TRANSPARENT));
return dialog;
}
public boolean isShowing() {
return getDialog() != null;
}
}
I have used the dialog fragment throughout my app with no issues, it shows up without issue in lots of places when i call dialog.show(getFragmentManager, null), however when I try to call it in onResume of my settings activity it does not show!
I have an activity for settings, which launches the system settings to change the language of the phone. Once the user changes the language and my activity resumes I detect if the language has changed and do a network call:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
mLoading = new NativeLoadingDialogFragment();
if (savedInstanceState != null) {
if (savedInstanceState.containsKey(EXTRA_LANGUAGE)) {
String language = savedInstanceState.getString(EXTRA_LANGUAGE);
String currentLanguage = AppUtils.getDefaultLanguageCode(
SmartBankConstant.DEFAULT_LANGUAGE,
SmartBankConstant.SUPPORTED_LANGUAGES);
if (!language.equals(currentLanguage)) {
updateLanguage(Language.stringToLanguage(currentLanguage));
}
}
}
}
private void updateLanguage(Language newLanguage) {
....
getSpiceManager().execute(new SetLanguageRequest(newLanguage),
new SetLanguageRequestListener(this));
mLoading.show(getFragmentManager(), null);
getFragmentManager().executePendingTransactions();
}
The code definitely runs but no dialog appears! If the network call fails I have a retry option that calls the updateLanguage(Language newLanguage) method again, and the dialog actually appears that time! What am I doing wrong?
Try with this approach. It checks if the dialog is already displayed, otherwise it shows it.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
mLoading = new NativeLoadingDialogFragment();
if (savedInstanceState != null) {
...
}
mLoading.show(getFragmentManager(), null);
}
private void updateLanguage(Language newLanguage) {
...
if (mLoading != null && !mLoading.isVisible()) {
mLoading.show(getFragmentManager(), null);
getFragmentManager().executePendingTransactions();
}
}
I don't know why, but running fragment transaction in the next loop helped to solve this issue.
new Handler().post(new Runnable() {
#Override public void run() {
// for some reason this must be called in the next loop
dialogFragment.show(getSupportFragmentManager(), tag);
}
});
I have a custom preference, TimePreference, which extends DialogPreference. It has a custom dialog resource, which looks like this
The source is
#Override
protected void onBindDialogView(View v){
super.onBindDialogView(v);
v.findViewById(R.id.butCancel).setOnClickListener(onClickL);
v.findViewById(R.id.butNow).setOnClickListener(onClickL);
v.findViewById(R.id.butOK).setOnClickListener(onClickL);
//....
}
//...
private final View.OnClickListener onClickL = new View.OnClickListener(){
#Override
public void onClick(View v) {
Log.d(lTag, v + " clicked");
switch (v.getId()) {
case R.id.butOK: saveToSP(false);break;
case R.id.butNow: saveToSP(true);
}
try {
getDialog().dismiss(); //may throw null pointer
} catch (Exception e) { Log.w(lTag, "Exc #onClickL", e); }
}
};
//...
I found a bug where, if you clicked the same preference really fast twice (at the preference screen) two dialogs would open. You could close the first one but, when you would try to close the second, the app would crash. It was a NullPointerException, so I enclosed it in a try-catch block. Now, the exception is caught, but the buttons do not close the dialog. Notice that, by clicking back, it does close.
How can I close the second dialog (possibly by simulating the behaviour of the back button?) ? Note, I want the API level below 10.
Okay, I found a soultion. I have a static boolean, which shows if there is an open dialog.
private static boolean isAnyDialogOpen = false;
On dialog bind, I set it to true,
And after I close the dialog, I set it to false.
Turned out that even this was problematic, but the solution was easier
#Override
protected void onClick() {
if (isAnyDialogOpen)
Log.i(lTag, "there is a dialog already");
else {
isAnyDialogOpen = true;
super.onClick();
}
}
#Override
public void onDismiss(DialogInterface dialog) {
Log.d(lTag, "dismiss, dialog= "+dialog);
isAnyDialogOpen = false;
if (dialog != null) super.onDismiss(dialog);
}
I have the following:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
....
populate();
handler = new Handler();
t = new Timer();
t.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
runOnUiThread(new Runnable() {
public void run() {
doReload1();
populate();
}
});
}
}, 300, 30000);
}
private void populate() {
if (playlists.length != 0) {
MyListView = (ListView) findViewById(R.id.MyListView);
for (String item : playlists) {
Log.d("DEBUG", "item=" + item);
}
String[] adapterPlaylists = new String[playlists.length];
for (int i = 0; i < playlists.length; i++) {
adapterPlaylists[i] = playlists[i];
}
adapter1 = new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, adapterPlaylists);
MyListView.setAdapter(adapter1);
adapter1.notifyDataSetChanged();
MyListView.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> arg0, View v,
int position, long id) {
dialogwait = ProgressDialog.show(Playlist.this,
"Loading...", "Please wait..", true);
Intent i = new Intent(getBaseContext(), ViewPlaylist.class);
i.putExtra("id", idPlaylist[position]);
i.putExtra("timer", timerPlaylist[position]);
startActivity(i);
finish();
}
});
} else
System.out.println("playlist null");
}
#Override
protected void onPause() {
super.onPause();
System.out.println("onPause Playlist!!!!!!");
dialogwait.dismiss();
t.cancel();
}
The thing is that here:
dialogwait = ProgressDialog.show(Playlist.this,
"Loading...", "Please wait..", true);
I create a ProgressDialog and I dissmis it in onPause().
But onPause gets called right after onCreate() before I even the ProgressDialog is created.
Any idea why?ANy solution?Thanks
This is because a Dialog in Android, does not block - meaning that the thread running behind it (in this case your Activity and in particular your onItemClickListener) will continue to execute.
It looks like you want to display a loading dialog, to let the user know that the item he clicked is being loaded. I suggest you move that dialog to the next activity (the one started from onClick), and then display and dismiss the dialog from there.
I think that the problem could be that you are calling finish() in your OnItemClickListener. This will cause the current Activity to end as soon as you click on an item which, because of your onPause() implementation will immediately close the dialog.
Maybe you need to display the dialog when your ViewPlaylist Activity is created rather than in this Activity.
anything on top of your current activity (a dialog or any other activity) makes it a background activity, thus onPause is called. if anything, dialog.dismiss() should be called in activities' onResume. in your implementation, you're showing a dialog when a button is clicked, thus pausing your activity and calling dismiss. you should call dismiss only when the process associated with your dialog is finished, thus telling your user that you're ready to do something else - in your case launch another activity.
launch your activity inside your dialog's onDismiss.
for example:
AlertDialog alert = new AlertDialog.Builder(this).create();
alert.setOnDismissListener(new OnDismissListener() {
#Override
public void onDismiss(DialogInterface dialog) {
// start the other activity
}
});