DialogFragment dismiss() does not pop backstack - android

I have a simple DialogFragment that calls dismiss when exits, according to the documentation:
public void dismiss()
Dismiss the fragment and its dialog. If the fragment was added to the
back stack, all back stack state up to and including this entry will
be popped. Otherwise, a new transaction will be committed to remove
the fragment.
however, I found that the fragment is still on the backstack after calling dismiss() so I have to click back button to clear it. Does anyone know why ?
here's my code:
public void onCreate(Bundle b) {
super.onCreate(b);
setContentView(R.layout.test_layout);
class MyDialogFragment extends DialogFragment implements OnClickListener{
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.hello_world, container, false);
Button b = (Button)v.findViewById(R.id.btn);
b.setOnClickListener(this);
return v;
}
#Override
public void onClick(View v) {
dismiss();
}
}
getFragmentManager().beginTransaction().add(android.R.id.content, new MyDialogFragment(), "test").addToBackStack("b").commit();
}
#Override
public void onBackPressed() {
if (getFragmentManager().getBackStackEntryCount() > 0 ){
getFragmentManager().popBackStack();
} else {
super.onBackPressed();
}
}
}
I also found out that if I don't override onBackPressed(), the back button simple doesn't work, no matter how many fragments I add to the activity, the back button always exits the activity right away.

I can confirm what #Luksprog said in his comment: the dialog must be started through show(FragmentTransaction, String).
Note after looking the source: make sure to call addToBackStack(String) on the supplied transaction or else it still won't work.

That it's a wrong way to create a DialogFragment.
Never ever use the FragmentManager to show a DialogFragment. To be shown there are a method called show(FragmentTransacion, String).
In java:
MyDialogFragment mDialogFragment = new MyDialogFragment();
mDialogFragment.show(getFragmentManager(), "MyDialogFragment");
For another hand, to dismiss the dialog just do this:
mDialogFragment.dismiss()
Another think that I would like to highlight is that the MyDialogFragment class is defined inner onCreate method :'(
Please, define the class outside the method or in another file if you want :)
Good Look!

dismiss()
findNavController().navigate(FirstBottomSheetDialogDirections.actionFirstSheetToSecondSheet())
This code is always the wrong thing to do: dismiss() is an asynchronous operation that doesn't actually dismiss anything immediately. That is unlike the navigate() which does immediately update the NavController's state, stacking the new dialog destination on top of the previous one.
This means that when the asynchronous dismiss actually happens, it correctly removes the dialog and, because it is a navigation stack, removes everything on top of it - including your second dialog. However, due to a bug in the DialogFragmentNavigator, we don't actually dismiss that second dialog, which is why it appears to work, despite everything actually already being internally out of sync (thus causing the later crash).
The correct way to pop a destination and navigate to a new destination as an atomic, immediate operation is to use popUpTo and popUpToInclusive. Therefore you can fix the sample app by removing the call to dismiss() and updating the action to pop the first dialog as part of the navigate call:
<action
android:id="#+id/action_firstSheet_to_secondSheet"
app:destination="#id/secondSheet"
app:popUpTo="#id/firstSheet"
app:popUpToInclusive="true"/>
This correctly pops the first dialog off the back stack and then navigates to the new dialog destination.
please refer this link : https://issuetracker.google.com/issues/191073055

Related

Android: Prevent fragment from becoming "active" while replacing another

I have the following invocations:
context.getSupportFragmentManager().popBackStack(tag.name(), 1);
context.getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, fragment, tag.name()).addToBackStack(tag.name()).commit();
...while I have at least 2 other fragments on the backstack that were opened before. When these two commands will be executed and the latest fragment has been popped off the backstack, for a very short period of time, the fragment before this fragment is going to be active before the popped fragment has been replace by the given one. And that's the problem, because this fragment fetches data from server and is displaying a progress dialog. Sometimes this leads to race conditions and strange behaviour.
How can I prevent the "non-active" fragments from becoming active while replacing another fragment?
Here is a short explanation of the situation:
MainActivity -> opens Fragment1 -> opens Fragment2a -> opens EditActivity -> after "save action", Fragment2a will be popped and a new Fragment2b will be added into the fragment_container of the MainActivity. While this happens, Fragment1 is doing things, but it must not do this. I want to prevent Fragment1 to do any tasks. It should somehow just stay in background and "sleep".
What about using observer pattern? You create an interface with a method to set fragments to busy starte or do some logic.
All fragments implement this interfac and you create a list that contains these interface inside Activity. If a fragment is registered add this fragments to list or remove them with unregister methods for example.
(MyActivity)getActivity.register(this); can be called to register a fragment. Or you can call a method like in active fragment if you wish to set other fragments except this one as busy (MyActivity)getActivity.setActive(this) and inside MyActivity you can declare this method as
public void setActive(IStateController fragment) {
for(IStateController f: listOfSubscribers) {
if(f == fragment) {
// This fragment called the method and should active
// other fragments can be set to busy or waiting state
}
}
}
I really can't say if it works for you but interacting with fragments without being aware of each other can be done this way, or you can check EventBus library.
Just check
if(getActivity() == null || !isAdded){
return; //don't do your code that touches UI if it is not active.
}
but make sure you have your onPause remove your busy indicator then if you don't plan to wait for completion or you will create a memory leak of window leaking.
I'm not sure if I misuse the fragment manager concept so that this situation can occur or if the fragment manager concept is a total crap (like a lot in Android), but I solved it by using some workaround. When I start or replace a new fragment, I store immediately an ID in the application context. When a Fragment is getting started, it checks if is has the correct id. If not, I return a null view and the fragment won't be created. The check looks like this:
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState)
{
return FragmentCreationHelper.createFragmentView(inflater, container, TAG, getFragmentId(), R.layout.fragment_mycoolview);
}
The helper class...
public class FragmentCreationHelper
{
public static View createFragmentView(LayoutInflater inflater, ViewGroup container, String loggingTag, FragmentTag fragmentId, int template)
{
Log.d(loggingTag, "onCreateView()");
if (MyContext.getNextVisibleFragment() == null || fragmentId.equals(MyContext.getNextVisibleFragment()))
{
Log.d(loggingTag, "inflating view...");
return inflater.inflate(template, container, false);
}
else
{
Log.d(loggingTag, "Skipping view inflation. Fragment should not be displayed.");
return null;
}
}
}

Looking for best way to implement modal dialog in onPause

I have fragment with EditTexts that user can change.
As well I have back button on Toolbar (and physical back button too)
When the user hits back, I check if the data was changed and if it was - I need to open dialog and ask the user "Do you want to save changes?". Get the click and act accordingly (positive or negative answer).
The best place to save the data (maybe I wrong) is in onPause of this fragment.
The problem is with the dialog - it is not modal and while it's showing the question and waits for user reaction - the fragment under it disappears and previous come back from stack.
I need to "pause the onPause" with the dialog until the user make his choice. What the easy (or most correct) way to do it?
#Override
public void onPause() {
if (!(text.getText().toString().equals(user.getName())))
{
new MaterialDialog.Builder(getActivity())
.title("Save changes?")
.content("You changed you personal details, save changes?")
.cancelable(false)
.positiveText("Save")
.negativeText("Discard")
.onPositive(new MaterialDialog.SingleButtonCallback() {
#Override
public void onClick(#NonNull MaterialDialog dialog, #NonNull DialogAction which) {
save();
}})
.show();
}
super.onPause();
}
If I want to do it before onPause - I'll need to catch the Toolbar's back button and physical back button - seems too much work for this. Looking for elegant way.
Thank you
onPause is called when the activity moves into the background (not being shown on the screen anymore).
To show a dialog before the app goes off screen, you can override the "onBackPressed" function in an activity.
#Override
public void onBackPressed() {
performBackPressed();
}
public void performBackPressed(){
//show your dialog here
//call finish(); when done to close app
}
You can call the performBackPressed() method whenever the Toolbar's back button is pressed too.
If you're trying to show a popup from a fragment when back is pressed, then you still have to override that method in the activity, then notifiy the fragment whenever back is pressed.
In my app I have a container in the activity that holds all my fragments. I use this code to get the currently active fragment:
YourFragmentClass curFrag = (YourFragmentClass) fragmentManager.findFragmentById(R.id.fragment_container);
then I have a method in the fragment called onBackPressed() and I just call that on the fragment I just got.

DialogFragment findFragmentByTag return null

I use DialogFragment contain a webview. After show the DialogFragment , when pressed back button, the app will return to the activity view. If pressed show DialogFragment button, the webview load the init url again. But my requirement is that if second time open the DialogFragment , i want the webview show page last time loaded. How can i achieve this?
My current thought is keep the DialogFragment instance, when push the show button then check if activity already has the DialogFragment. But in my code, the findFragmentByTag always return null ,what's the problem? Or I should do some work in the DialogFragment?
main activity code:
mcWebViewDialog m_webviewDialog = null;
public void showWebviewDialog()
{
FragmentManager fragmentManager=getFragmentManager();
FragmentTransaction fragmentTransaction=fragmentManager.beginTransaction();
Fragment prev=fragmentManager.findFragmentByTag("mcWebDialog");
if(prev!=null)
{
Log.v("seayoung","m_webviewDialog not null");
fragmentTransaction.show(prev);
}
else
{
Log.v("seayoung","m_webviewDialog null");
m_webviewDialog=mcWebViewDialog.newInstance();
m_webviewDialog.set_loadurl("file:///android_asset/input.html");
m_webviewDialog.show(fragmentManager,"mcWebDialog");
}
}
WebViewDialog code:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
//other code
m_webView.loadUrl(m_loadurl);
return m_webView;
}
When you press back your dialog fragment is destroyed and is removed from backstack, hence you are getting null while performing findFragmentByTag, well it wont be a good idea to hold the view when user presses back, but as an alternative you can try caching the created fragment locally, like this.
HashMap<String,WeakReference<DialogFragment>> dialogMap = new HashMap<>();
// call this before callin `show`
dialogMap.put(TAG,new WeakReference<>(YOUR_FRAGMENT));
and next time, you can perform this
DialogFragment prev=fragmentManager.findFragmentByTag("mcWebDialog");
if(prev == null){
prev = dialogMap.get("mcWebDialog").get();
}
if(prev!=null){
Log.v("seayoung","m_webviewDialog not null");
fragmentTransaction.show(prev);
}

How to correctly dismiss a DialogFragment?

The docs say this for the dismiss() method from the Dialog class:
Dismiss this dialog, removing it from the screen. This method can be invoked
safely from any thread. Note that you should not override this method to do
cleanup when the dialog is dismissed, instead implement that in onStop().
In my code, all I do is call getDialog().dismiss() to dismiss it. But I am not doing anything else or even using onStop(). So I am asking exactly how to correctly dismiss a DialogFragment to avoid any memory leaks, etc..
tl;dr: The correct way to close a DialogFragment is to use dismiss() directly on the DialogFragment.
Details: The documentation of DialogFragment states
Control of the dialog (deciding when to show, hide, dismiss it) should be done through the API here, not with direct calls on the dialog.
Thus, you should not use getDialog().dismiss(), since that would invoke dismiss() on the dialog. Instead, you should use the dismiss() method of the DialogFragment itself:
public void dismiss()
Dismiss the fragment and its dialog. If the fragment was added to the back stack, all back stack state up to and including this entry will be popped. Otherwise, a new transaction will be committed to remove the fragment.
As you can see, this takes care not only of closing the dialog but also of handling the fragment transactions involved in the process.
You only need to use onStop if you explicitly created any resources that require manual cleanup (closing files, closing cursors, etc.). Even then, I would override onStop of the DialogFragment rather than onStop of the underlying Dialog.
I think a better way to close a DialogFragment is this:
Fragment prev = getSupportFragmentManager().findFragmentByTag("fragment_dialog");
if (prev != null) {
DialogFragment df = (DialogFragment) prev;
df.dismiss();
}
This way you dont have to hold a reference to the DialogFragment and can close it from everywhere.
Why don't you try using only this code:
dismiss();
If you want to dismiss the Dialog Fragment by its own. You can simply put this code inside the dialog fragment where you want to dismiss the Dialog.
For example:
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
dismiss();
}
});
This will close the recent Dialog Fragment that is shown on the screen.
Hope it helps for you.
I gave an upvote to Terel's answer. I just wanted to post this for any Kotlin users:
supportFragmentManager.findFragmentByTag(TAG_DIALOG)?.let {
(it as DialogFragment).dismiss()
}
Kotlin Version of Terel answer
(fragmentManager.findFragmentByTag(TAG) as? DialogFragment)?.dismiss()
You should dismiss you Dialog in onPause() so override it.
Also before dismissing you can check for null and is showing like below snippet:
#Override
protected void onPause() {
super.onPause();
if (dialog != null && dialog.isShowing()) {
dialog.dismiss();
}
}
There are references to the official docs (DialogFragment Reference) in other answers, but no mention of the example given there:
void showDialog() {
mStackLevel++;
// DialogFragment.show() will take care of adding the fragment
// in a transaction. We also want to remove any currently showing
// dialog, so make our own transaction and take care of that here.
FragmentTransaction ft = getFragmentManager().beginTransaction();
Fragment prev = getFragmentManager().findFragmentByTag("dialog");
if (prev != null) {
ft.remove(prev);
}
ft.addToBackStack(null);
// Create and show the dialog.
DialogFragment newFragment = MyDialogFragment.newInstance(mStackLevel);
newFragment.show(ft, "dialog");
}
This removes any currently shown dialog, creates a new DialogFragment
with an argument, and shows it as a new state on the back stack. When
the transaction is popped, the current DialogFragment and its Dialog
will be destroyed, and the previous one (if any) re-shown. Note that
in this case DialogFragment will take care of popping the transaction
of the Dialog is dismissed separately from it.
For my needs I changed it to:
FragmentManager manager = getSupportFragmentManager();
Fragment prev = manager.findFragmentByTag(TAG);
if (prev != null) {
manager.beginTransaction().remove(prev).commit();
}
MyDialogFragment fragment = new MyDialogFragment();
fragment.show(manager, TAG);
CustomFragment dialog = (CustomDataFragment) getSupportFragmentManager().findFragmentByTag("Fragment_TAG");
if (dialog != null) {
dialog.dismiss();
}
Adding to the other answers, when having a DialogFragment that is full screen calling dismiss() won't pop the DialogFragment from the fragment backstack. A workaround is to call onBackPressed() on the parent activity.
Something like this:
CustomDialogFragment.kt
closeButton.onClick {
requireActivity().onBackPressed()
}
I found that when my fragment was defined in the navigation graph with a <fragment> tag (for a full screen dialogfragment), the dialogfragment would not dismiss with the dismiss() command. Instead, I had to pop the back stack:
findNavController(getActivity(), R.id.nav_host_fragment).popBackStack();
However, if the same dialogfragment was defined in the navigation graph with a <dialog> tag, dismiss() works fine.
Just call dismiss() from the fragment you want to dismiss.
imageView3.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
dismiss();
}
});
Consider the below sample code snippet which demonstrates how to dismiss a dialog fragment safely:
DialogFragment dialogFragment = new DialogFragment();
/**
* do something
*/
// Now you want to dismiss the dialog fragment
if (dialogFragment.getDialog() != null && dialogFragment.getDialog().isShowing())
{
// Dismiss the dialog
dialogFragment.dismiss();
}
Happy Coding!
Here is a simple AppCompatActivity extension function, which closes opened Dialog Fragment:
fun AppCompatActivity.whenDialogOpenDismiss(
tag: String
) {
supportFragmentManager.findFragmentByTag(tag)?.let {
if(it is DialogFragment) it.dismiss() }
}
Of course you can call it from any activity directly.
If you need to call it from a Fragment just make the same extension function about Fragment class

Android: FragmentTransaction hide doesn't work for DialogFragment

I'm trying to add a
fragmentTransaction.hide(myDialogFragment);
fragmentTransaction.addToBackStack(null);
to a FragmentTransaction so that the dialog will re-appear when the user hits the back button, but it's not working. I originally overrode onCreateDialog in my DialogFragment, but I noticed that the documentation for the hide call on FragmentTransaction states:
This is only relevant for fragments whose views have been added to a
container.
So instead, now I'm overriding onCreateView. Now it sort of hides, but not really. The dialog merely shrivels, but the window still remains dark. I have to hit the back button to get rid of it, which is not the behavior I want, obviously. What am I missing here?
A DialogFragment maintains a dialog internally and calls show and hide methods on it according to its own lifecycle. Calling FragmentTransaction.hide() just tries to set the visibility of the fragment's view, as returned by Fragment.onCreateView(), to View.GONE. The view of the DialogFragment is coincidently the view used for its internal dialog, and so what you are doing is hiding the content on the dialog. Unfortunately, hiding the view does not 'dismiss' the dialog and so the screen will still be dimmed.
When you call DialogFragment.show(FragmentTransaction,String), a FragmentTransaction is created to add it to the FragmentManager. Ordinarily, showing the dialog is considered the 'active' transaction, and then dismissing it is just popping the back stack the appropriate number of times. If you did not add any other fragments in between, then a new FragmentTransaction is created with a remove operation. If we could access this, then we could just add a backstack entry and make this operation reversible. Unfortunately, this is not possible and so the best we can do is just to make our own dismiss method (and hope the internal state does not get too screwed up):
public class UndoDialogFragmentActivity extends FragmentActivity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// show a dialog fragment in the normal way
new MyDialogFragment().show(getSupportFragmentManager(), "dialog");
}
});
}
private static class MyDialogFragment extends DialogFragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setStyle(STYLE_NO_TITLE, getTheme());
// do not allow back button to dismiss dialog; confusing behaviour otherwise!
setCancelable(false);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Button button = new Button(getActivity());
button.setText("Dismiss");
button.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// pressing back after 'dismissing' the dialog will cause it to be added again
getFragmentManager().beginTransaction().remove(MyDialogFragment.this).addToBackStack(null).commit();
}
});
return button;
}
}
}
Clicking on the button in the fragment will cause a DialogFragment to be opened, with its own dismiss button. After pressing dismiss, you can show the dialog again by pressing the back key, undoing the remove operation. This produces somewhat questionable behaviour when you allow the back key to both show and hide the dialog, but the details can be decided by you according to your application.
I was able to hide a dialog of a DialogFragment by calling getDialog().hide() from within my DialogFragment.
If you're using API Level 11 or higher, you can simply call dismiss() on the DialogFragment, either from FragmentActivity or from DialogFragment itself.

Categories

Resources