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.
Related
I have a custom dialog which extends AppCompatDialogFragment. I am calling it from within one Fragment of a BottomNavigationBar. It gets dismissed when a message is returned from a ListenerService. It all works well until I switch to a different fragment using one of the icons on the BottomNavigationBar and then back to the fragment with Dialog functionality. The next time I show the dialog the message does not dismiss it.
I have a member variable:
SendingMessageDialog sendingMessageDialog;
When I click a button it calls this to show the dialog:
sendingMessageDialog = new SendingMessageDialog();
sendingMessageDialog.show(getActivity().getSupportFragmentManager(), "Sending Message");
When the message is receive I call this:
sendingMessageDialog.dismiss();
sendingMessageDialog = null;
I have also tried it this way:
private void dismissDialog() {
Fragment prev = getActivity().getSupportFragmentManager().findFragmentByTag("Sending Message");
if (prev != null) {
Log.d(TAG, "dismissDialog: prev is not null");
SendingMessageDialog df = (SendingMessageDialog) prev;
df.dismiss();
}
}
My SendingMessageDialog which extends AppCompatDialogFragment, I have this code in my onCreateDialog:
#NonNull
#Override
public Dialog onCreateDialog(#Nullable Bundle savedInstanceState) {
Log.d(TAG, "onCreateDialog: starting");
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
LayoutInflater inflater = getActivity().getLayoutInflater();
View view = inflater.inflate(R.layout.send_message_dialog, null);
builder.setView(view)
.setTitle("Sending Token")
.setNegativeButton("cancel", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialogInterface, int i) {
}
});
return builder.create();
}
I have looked up a few post on stackoverflow but none seem to address this specific issue. I saw this one, but there is no cancel() method available. I saw this one but I have to dismiss the dialog when I get a message from another part of the system, so I cannot just do this in-line sort of thing. This one just seemed like a simple mistake of calling show on the wrong dialog. I couldn't quite get a handle on this one, but it's 8 years old and seems to be about the coordination of two dialogs.
Many of the examples identify the dismiss is not working. In my case it is, until I switch fragments using the bottom navigation and then return to the fragment where the dialog process exists.
Nothing special happening in the navigation:
selectedFragment = new RunScenarioListFragment();
and:
getActivity().getSupportFragmentManager()
.beginTransaction()
.replace(R.id.frame_layout_run_scenario,selectedFragment)
.addToBackStack(null)
.commit();
I have a MainActivity containing 5 fragments, 2 of which have a help icon on the toolbar on top right. I have hidden this icon on other 3 fragments. Upon clicking help icon, an alert dialog shows up with title, message and a positive button.
This is my Alert Dialog code:
public class HelpDialogFragment extends DialogFragment {
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle("Help");
builder.setMessage("Placeholder");
builder.setPositiveButton("Got It", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {}
});
return builder.create();
}
}
and this is how I am showing it from MainActivity:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_help:
DialogFragment helpDialog = new HelpDialogFragment();
helpDialog.show(getSupportFragmentManager(), "dialogHelp");
return true;
}
return super.onOptionsItemSelected(item);
}
The above code works but I would like to show different message based on the fragment selected so how to change the message? I tried this to change title
helpDialog.getDialog().setTitle("Some Text");
Please note I want to change Dialog message, i.e main content, I only got setTitle() method on getDialog() and not setMessage(), the above setTitle is just for example purpose but even it is throwing NullPointerException.
As you can see in the above screenshot, "Placeholder" text is the default text I added at the time of creating AlertDialog but now how to change it?
From reading your post and comments it looks like you need to set different titles depending on whatever fragment is visible. And the creation of dialogs happens from Activity so you are not sure what title to set.
The problem is essentially identifying the visible fragment and set message according to it.
You can pass the message with arguments like this.
Fragment fragment = new Fragment();
Bundle bundle = new Bundle();
bundle.putString(message, "My title");
fragment.setArguments(bundle);
Then in your Fragment, get the data (e.g. in onCreate() method)
Bundle bundle = this.getArguments();
if (bundle != null) {
String message = bundle.getString(message, defaultValue);
}
How to identify the currently visible fragment? You can do this as suggested in these answers. Once you get the current fragment, just send the message in the arguments above according to it.
By combining the above 2 ideas you can do this.
Another way would be to start the dialog from the fragment and not from the Activity but that would involve more changes so the above approach is better.
First pass the required message over a bundle while calling HelpDialogFragment class
HelpDialogFragment helpDialog = new HelpDialogFragment();
Bundle bundle = new Bundle();
bundle.putString("placeholder", "Custom placeholder");
helpDialog.setArguments(bundle);
helpDialog.show(getSupportFragmentManager(), "dialogHelp");
Now modify your HelpDialogFragment class create the dialog like this
public class HelpDialogFragment extends DialogFragment {
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle("Help");
if (getArguments() != null)
builder.setMessage(getArguments().getString("placeholder",""));
builder.setPositiveButton("Got It", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {}
});
return builder.create();
}
}
I have a Dialog prompt in mainActivity. If user close the Dialog, I need to show red dot to one of the ListView items inside fourthFragment(as per redbox in fourthFragment in image below).
The problem is the red dot only be updated after I close the app and reopened it, because fourthFragment already done created BEFORE user close the Dialog. How can I refresh/update the fourthFragment after Dialog closed so that red dot can be shown immediately?
short description:
mainActivity: on Dialog closed > store showRedDot="1" to local db
fourthFragment: onCreate > read showRedDot from local db, if "1", show red dot. ( problem here, when onCreate, showRedDot is still "0", so I need to update fourthFragment layout after dialog closed.)
Try Interface or Broadcast receivers as above. If else refresh the fragment as
// Reload current fragment
Fragment frg = null;
frg = getSupportFragmentManager().findFragmentByTag("Your_Fragment_TAG");
final FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.detach(frg);
ft.attach(frg);
ft.commit();
You have calling a interface to push event dismiss(button click) of dialog.
Like this:
public interface MyDialogListener {
void OnCloseDialog();
}
public class fourthFragment extend Fragment implements MyDialogListener {
public void SomeMethod() {
MyDialog myDialog = new MyDialog(this, this);
myDialog.show();
}
public void OnCloseDialog() {
// Do update your listview in here( maybe call method initialize data for listview)
}
}
public class MyDialog extends Dialog {
MyDialogListener mListener;
public MyDialog (Context context, MyDialogListener listener) {
super(context, R.style.Dialog);
mListener = listener;
}
public void onClick(View view) {
switch (view.getId()) {
case R.id.CloseButton:
// Push event when Dialog close(or anything)
mListener.OnCloseDialog();
dismiss()
break;
default:
//...
}
}
}
I had a working DialogFragment that was using an inner class to do a bunch of things on some objects, set menu icons etc. When i went to Android Studio i realised that was incorrect and i've been trying to change the inner class to be static.
In so doing, I am now trying to use onCreateDialog to, as per Google docs, "doPositiveClick" and "doNegativeClick", so that the calling MainActivity can do the work on those objects instead of the fragment doing it.
What is now confusing me however, is how do I set the layout in the fragment - I can enter a title, message and buttons as such:
return new AlertDialog.Builder(getActivity())
.setTitle(R.string.alert_title)
.setMessage(R.string.alert_message)
.setPositiveButton(R.string.set,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
((MainActivity)getActivity()).doPositiveClick();
}
}
)
.setNegativeButton(R.string.cancel,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
((MainActivity)getActivity()).doNegativeClick();
}
}
)
But previously I was doing the layout like:
final EditText input = new EditText(MainActivity.this);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(20, 20);
input.setText("5");
input.setLayoutParams(lp);
input.setRawInputType(Configuration.KEYBOARD_QWERTY);
Problem is, where does this go in onCreateDialog ? The Google docs shows how to set text on a dialog textView, but that is within onCreateView().
My confusion is that the google doc doesnt do both, ie, it doesnt show how to both, set up custom elements, AND set up the positive/negative click in the calling MainActivity - or if it does, i'm sorry I cant see it right now.
So can anyone make it clearer for me, using the above onCreateDialog, how can I have an editText field, with a default value that takes user input, and then get back that input to the doPositiveClick() to process.
DialogFragment can use in 2 ways: dialog or view.
case1: use DialogFragment as a dialog. you have to implement onCreateDialog() to return a dialog. and then have to show the dialog in the following way. see the example:
public static class MyAlertDialogFragment extends DialogFragment {
public static MyAlertDialogFragment newInstance(int title) {
MyAlertDialogFragment frag = new MyAlertDialogFragment();
Bundle args = new Bundle();
args.putInt("title", title);
frag.setArguments(args);
return frag;
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
int title = getArguments().getInt("title");
return new AlertDialog.Builder(getActivity())
.setIcon(R.drawable.alert_dialog_icon)
.setTitle(title)
.setPositiveButton(R.string.alert_dialog_ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
((FragmentAlertDialog)getActivity()).doPositiveClick();
}
}
)
.setNegativeButton(R.string.alert_dialog_cancel,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
((FragmentAlertDialog)getActivity()).doNegativeClick();
}
}
)
.create();
}
}
Create and have to show dialog as the following way. show this way don't care, whether onCreateView() is implemented or not.
// Create the fragment and show it as a dialog.
DialogFragment newFragment = MyDialogFragment.newInstance();
newFragment.show(getFragmentManager(), "dialog");
case2: use as view (it is not feature of dialog). it is only view. you have to implement onCreateView() and show dialog as the following way:
public static class MyDialogFragment extends DialogFragment {
static MyDialogFragment newInstance() {
return new MyDialogFragment();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.hello_world, container, false);
View tv = v.findViewById(R.id.text);
((TextView)tv).setText("This is an instance of MyDialogFragment");
return v;
}
}
and have to show view as follow. the same as use Fragment class. show this way don't care, whether onCreateDialog() is implemented or not.
FragmentTransaction ft = getFragmentManager().beginTransaction();
DialogFragment newFragment = MyDialogFragment.newInstance();
ft.add(R.id.embedded, newFragment);
ft.commit();
summary:
in design, you can implement onCreateView() and onCreateDialog() together and use the same source code with this DialogFragment lifecycle. If the screen is small, use DialogFragment as Dialog. If the screen is big, use DialogFragment as view (the same common Fragment class).
Notice that use the correct way to show DialogFragment to suitable with onCreateView() and onCreateDialog() to prevent exception.
sorry guys I thought I exhausted my searches but just after I posted this I was able to fix it, putting the text field/layout inside the onCreateDialog BEFORE the Builder and then doing setView() to that input as such:
**LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(20, 20);
final EditText input = new EditText(getActivity());
input.setText("5");
input.setLayoutParams(lp);
input.setRawInputType(Configuration.KEYBOARD_QWERTY);**
// Use the Builder class for convenient dialog construction
return new AlertDialog.Builder(getActivity())
.setTitle(R.string.alert_title)
.setMessage(R.string.alert_message)
**.setView(input)**
.setPositiveButton(R.string.set,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
((MainActivity)getActivity()).doPositiveClick();
}
}
)
Only question now is how do I get the value back after the user inputs ?
I am trying to get the Dialog I have created with an extended DialogFragment using DialogFragment.getDialog() but it returns null.
Basically I want to alter the text in the layout from the FragmentActivity which creates and shows the DialogFragment.
You're calling getDialog() too early in the DialogFragment's life cycle.
getDialog() simply returns the private variable mDialog from the DialogFragment.
When a DialogFragment is instantiated mDialog is null, and then it gets set when onCreateDialog is fired inside getLayoutInflater(Bundle savedInstanceState), so you have to call getDialog after onCreateDialog.
For example, the order of some common methods called is onCreate, onCreateDialog, and onCreateView, onStart. So, you can call getDialog and have it return something in onCreateView or onStart, but not in onCreate or onCreateDialog.
Even though onStart is called called when the Fragment is visible to the user, adjusting the layout of the fragment at that point looks fine.... for example setting the width and height using getDialog().getWindow().setLayout(..., ...); doesn't make the fragment appear to change size, but just appears to have the newly set size.
Try calling executePendingTransactions() from the available FragmentManager.
dialogFragment = new DialogFragment();
...
dialogFragment.show(mFragmentActivity.getSupportFragmentManager(), "Dialog");
mFragmentActivity.getSupportFragmentManager().executePendingTransactions();
Dialog d = dialogFragment.getDialog()
...
There is 2 way to show DialogFragment:
void showDialog() {
// Create the fragment and show it as a dialog.
DialogFragment newFragment = MyDialogFragment.newInstance();
newFragment.show(getFragmentManager(), "dialog");
}
And
FragmentTransaction ft = getFragmentManager().beginTransaction();
DialogFragment newFragment = MyDialogFragment.newInstance();
ft.add(R.id.embedded, newFragment);
ft.commit();
You can only get a nonNull dialog when using the first way.
public class Dialog extends DialogFragment {
private DialogListener dialogListener;
public void setDialogListener(DialogListener dialogListener) {
this.dialogListener = dialogListener;
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
view = inflater.inflate(R.layout.layout_dialog, null);
return view;
}
#Override
public void onDismiss(DialogInterface dialog) {
super.onDismiss(dialog);
if (null != dialogListener) {
dialogListener.onDismiss();
}
}
public interface DialogListener {
void onDismiss();
}
}
in Activity
...
Dialog dialog= new Dialog();
dialog.setDialogListener(new Dialog.DialogListener() {
#Override
public void onDismiss() {
Foo()..
}
});
One reason for why getDialog() might return null after the dialog has been constructed and properly stored in mDialog is an accidental invocation of dismiss() on the DialogFragment.
When dismiss() is called, it will reset the mDialog field to null so that subsequent invocations of getDialog() will return null instead of the previously constructed dialog.
In my case, dismiss() was called to handle an error situation / side-case in the DialogFragment's onActivityCreated() method. Subsequently trying to use getDialog() from the onResume() method returned null.
Also refer to the source code of the DialogFragment class, specifically its dismissInternal(boolean allowStateLoss) method:
https://github.com/aosp-mirror/platform_frameworks_base/blob/pie-platform-release/core/java/android/app/DialogFragment.java