Android Navigation Component not working with Dialog Fragments - android

Disclaimer: I've checked the documentation and since 2.1.0 the navigation components has supported Dialog Fragments. (https://developer.android.com/jetpack/androidx/releases/navigation#2.1.0)
Error That I'm Getting
I'm getting this error when trying to go from a DialogFragment to my Start Destination:
java.lang.IllegalStateException: Fragment PostDistressDialog{829f5d1} (bbbc4926-684b-491b-9772-e0f0ffebe0af)} not associated with a fragment manager.
PostDistressDialog is a DialogFragment called from JournalEntryFragment(can be seen in map below) using the navigation component. PostDistressDialog is not an inner class of JournalEntryFragment. It is in a class of its own extending DialogFragment
Picture of my Navigation Graph
Function Calling NavController
public class PostDistressDialog extends DialogFragment implements ISaveDatabase {
...
#NonNull
#Override
public Dialog onCreateDialog(#Nullable Bundle savedInstanceState) {
if (getArguments()!=null) {
...
// Set up the Alert Dialog
AlertDialog.Builder alertDialog = new AlertDialog.Builder(getContext());
alertDialog.setTitle(R.string.distressed_levels);
alertDialog.setMessage(R.string.distressed_how_feel_post);
// Inflate and set the layout for the dialog
View layout = View.inflate(getActivity(), R.layout.dialog_seekbar, null);
alertDialog.setView(layout);
....
// Add okay button
alertDialog.setPositiveButton(R.string.okay, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// Save post distress value in Journal Entry
mJournalEntry.setPostDistress(mTempDistressValue);
// Save to Journal Entry to database
// Check if journal entry empty
if(isJournalEntryEmpty(mJournalEntry)){
...
}
else{
// Give title if empty
if(mJournalEntry.getTitle().isEmpty()) {
....
// Save to database
new SaveDatabase(getContext(),PostDistressDialog.this).execute(mJournalEntry);
}
// Go to main menu
}
});
return alertDialog.create();
}
return null;
}
...
#Override
public void databaseSavingCompleted(){
NavHostFragment.findNavController(this).navigate(PostDistressDialogDirections.postDistressDialogToJournalListAction());
}
}
Where this is public class PostDistressDialog extends DialogFragment
Dialog in my Navigation XML File
<dialog
android:id="#+id/postDistressDialog"
android:name="com.dgrullon.cbtjourney.dialogs.PostDistressDialog"
android:label="PostDistressDialog" >
<argument
android:name="postDistressDialogArguments"
app:argType="com.dgrullon.cbtjourney.pojo.JournalEntries"/>
<action
android:id="#+id/postDistressDialog_to_journalListAction"
app:destination="#id/journalList"
app:popUpTo="#id/journalList"
app:popUpToInclusive="true" />
</dialog>

AlertDialog automatically dismisses the Dialog (and hence, removes your DialogFragment) when the callback you add to setPositiveButton is fired. Because you're doing work asynchronously, your databaseSavingCompleted method is called after the DialogFragment is destroyed, detached from the FragmentManager, and removed from the NavController - you're leaking a reference to your DialogFragment (as it would otherwise be garbage collected).
Therefore when NavHostFragment.findNavController(this) fires, all hooks that would let it access the NavController are already cleaned up.
If you don't want your button to immediately dismiss the dialog, you need to pass in null to setPositiveButton() and instead get a reference to the button after the dialog has been created by calling its getButton() API and manually setting an OnClickListener that would kick off your AsyncTask (and disable the button to prevent it from being clicked more than once).

Related

Android custom Dialog not showing after FragmentActivity recreate

On my app i have a ViewPager inside FragmentActivity that contain a fragments. In one of that fragment (called FragOne) i have a button that on tap open a custom Dialog (a Class extends android.app.Dialog) on certain position of screen:
myButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
MyCustomDialog = new MyCustomDialog(getContext());
dialog.getWindow().setFlags(
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
dialog.getWindow().getDecorView().setSystemUiVisibility(
getActivity().getWindow().getDecorView().getSystemUiVisibility());
dialog.show();
dialog.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
dialog.getWindow().setLayout(myWidth, myHeight);
}
});
When user perform some action on dialog some tasks are executed, recreate() of FragmentActivity is called, then dialog dismiss itself.
Now happen that if try to open again the dialog by press the button, i can't see it anymore. For make dialog appear i need to go to change viewpager current visible fragment, then go back to FragOne.
How can i do for let open dialog again without change fragment and go back to FragOne?

Sending data between fragments without creating new fragment

So I have a fragment (WifiSetupFragment) that calls a DialogFragment, and that DialogFragment needs to pass a string back to the original fragment. I know to do this you have an interface in the activity that will send data to the original fragment like so, which I am already doing:
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.addToBackStack(null);
transaction.replace(R.id.content_frag, WifiSetupFragment.newInstance(password));
transaction.commit();
So the first time I call WifiSetupFragment, I haven't created a DialogFragment yet because I haven't clicked on an item to open the dialog. My question is should I just call
WifiSetupFragment.newInstance(null)
and have a null check for the password string in my fragment? Because I don't have a password unless the DialogFragment is open, and it's not always open. If this made no sense, please tell me and I'll try to explain more clearly. I guess it just seems strange to me to have a parameter for a string that might only be sent to this fragment occasionally since the data isn't constantly being passed in.
You don't need to communicate between these Fragments through the Activity. What you can do instead:
Make your WifiSetupFragment.newInstance() accept no parameters.
Make WifiSetupFragment implement a new interface, let's call it OnPasswordSuppliedListener.
Once you create your DialogFragment instance, attach it to a getChildFragmentManager() instead of getFragmentManager().
Now inside of your DialogFragment subclass you can reference WifiSetupFragment by calling getParentFragment().
Cast getParentFragment() to your interface and voila!
Note: I'm assuming you're using Fragments from the support library. Otherwise please be aware that nested Fragments feature was introduced in the API 17.
Your dialog can define an interface allowing to send input password back to parent fragment / activity:
public class TestDialog extends DialogFragment {
private TextView mPasswordView;
private OnPasswordDefinedCallback mCallback;
public static TestDialog newInstance() {
TestDialog dialog = new TestDialog();
return dialog;
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// inflate layout for your dialog (it must include edit text for password)
LayoutInflater inflater = getActivity().getLayoutInflater();
View layout = inflater.inflate(R.layout.dialog_test, null);
// getting ui elements from layout
mPasswordView = (TextView) layout.findViewById(R.id.txt_password);
// building dialog
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setView(layout);
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
try {
mCallback = (OnPasswordDefinedCallback) getTargetFragment();
} catch (ClassCastException e) {
throw new ClassCastException("must implement OnPasswordDefinedCallback");
}
if (mCallback != null) {
// send password back to parent
mCallback.doPasswordDefined(mPasswordView.getText().toString());
}
dismiss();
}
});
return builder.create();
}
public interface OnPasswordDefinedCallback {
void doPasswordDefined(String password);
}
}
Then in WifiSetupFragment you can proceed as follows for opening PasswordDialog:
TestDialog dialog = TestDialog.newInstance();
dialog.setTargetFragment(WifiSetupFragment.this, 1);
dialog.show(getChildFragmentManager(), null);
WifiSetupFragment must of course implement interface OnPasswordDefinedCallback.

DialogFragment dismiss() does not pop backstack

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

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.

Reset Android DialogFragment

I am using a custom DialogFragment to let a user change his login credentials. There are some text fields and two buttons (save/cancel). The layout is set in DialogFragment's onCreateView method.
If I open the dialog text fields are filled with default values. When the user changes text in a text field and clicks the cancel button the dialog is dismissed. Next time the dialog opens the text field changed before does not contain the default value as i expected but the text the user changed before. The text fields are not reset. This is almost the same problem mentioned here Reset an Android Dialog. The problem is that the solution provided refers to a Dialog which is deprecated in API level 11 and i cannot use onPrepareDialog with a DialogFragment.
Is there a similar way to reset the content of a DialogFragment?
You can override onResume() in your class, which extends DialogFragmet, as follows:
private static class MyDialogFragment extends DialogFragment {
public static MyDialogFragment newInstance() {
// ...
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// ...
}
#Override
public void onResume() {
super.onResume();
Dialog dialog = getDialog();
// reset code goes here - use dialog as you would have in onPrepareDialog()
}
}
You can also use .setText() method in Your activity as reaction after negative button click. Eg:
In DialogFragment.java, onCreateDialog(...)define AlertDialog.Builder
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
then
//this is better than creating button in layout
builder.setNegativeButton(R.string.button_cancel,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
((MainAct) getActivity()).cancelDialog(DialogFragment.this);
}
}
);
In MainActivity.java create method cancelDialog(DialogFragment df) {
//here use df to reset text fields
}

Categories

Resources