I have created a DialogFragment with a custom AlertDialog that I need to show on several points of my application. This dialog asks for the user to input some data.
I would like to find a way to make the activity where the dialog is called upon to wait for the users input and then perform a variable action when the user presses the ok button (or nothing if he presses cancel).
AFAIK there's no "modal dialog" in Android so what would be the proper way to achieve this (pretty usual) kind of behavior?
To allow a Fragment to communicate up to its Activity, you can define an interface in the Fragment class and implement it within the Activity.
public class MyDialogFragment extends DialogFragment {
OnDialogDismissListener mCallback;
// Container Activity must implement this interface
public interface OnDialogDismissListener {
public void onDialogDismissListener(int position);
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception
try {
mCallback = (OnDialogDismissListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnDialogDismissListener");
}
}
...
}
In dialog Ok listener add
mCallback.onDialogDismissListener(position);
In your activity
public static class MainActivity extends Activity
implements MyDialogFragment.OnDialogDismissListener{
...
public void onDialogDismissListener(int position) {
// Do something here to display that article
}
}
Related
Is it possible for two (switch) buttons to have synchronized behavior between fragments? i.e: When button A is switched on in fragment A, I want button B's appearance in fragment B to also appear switched on.
How would I do that? The end goal is to have a global button on either fragment.
You can use a boolean flag in your activity and set it false by default and when any of switch is pressed on in either fragment then set its value to true, and when you navigate to another fragment then check flag value and if its true then switch it on or else off.
That would depend on whether both fragments are on same activity. If they are, then all you need is a boolean flag on the said activity and synchronise depend on that.
If not, then maybe create an boolean preference and synchronise based on that. Hope this helps. If you need code examples, let me know.
public interface Listener {
public boolean getFlag();
public void setFlag(boolean enable);
}
public class SomeActivity extends AppCompatActivity implements Listener {
// getFlag, setFlag implementation
}
public class FragmentA {
private boolean state;
private Listener listener;
private Switch switchBtn;
public void onAttach(Context ctx){
listener = (Listener) this.getActivity();
// check for ClassCast Exception
}
public void onActivityCreated() {
state = listener.getFlag();
switchBtn.setChecked(state);
}
}
For details view this page
Sample of communication between Fragments. Example below is modified from Communicating with Other Fragments
Step 1. Create an interface
public interface ButtonCallback{
void onClick(boolean val)
}
Step 2. In the HostActivity which hosts both fragment A and B, make HostActivity implements interface ButtonCallback.
public class HostActivity extends AppCompatActivity implements ButtonCallback{
void onClick(boolean val){
}
}
Step 3. In fragment A & B, initialize the callback with casting the activity
class FragmentA extends Fragment{
ButtonCallback callback;
#Override
public void onAttach(Context context) {
super.onAttach(context);
//Make sure activity host implement ButtonCallback interface
try {
callback= (ButtonCallback ) context;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement ButtonCallback");
}
}
//public method to update fragment's button state
public void setGlobalButtonState(boolean val){
//globalButton has been initialized in onCreateView function
globalButton.setEnabled(val);
}
}
class FragmentB extends Fragment{
ButtonCallback callback;
#Override
public void onAttach(Context context) {
super.onAttach(context);
try {
callback= (ButtonCallback ) context;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement ButtonCallback");
}
}
public void setGlobalButtonState(boolean val){
//globalButton has been initialized in onCreateView function
globalButton.setEnabled(val);
}
}
Step 4. In Fragment A, call ButtonCallback.onClick(boolean) when user click on the button
globalButton.setOnClickListener(new View.OnClickListener(View v){
Boolean value = !v.isEnabled();
callback.onClick(value)
});
Step 5. In HostActivity's onClick function, find a way to get Fragment B that suis your context, and update the button in Fragment B via the setGlobalButtonState function
void onClick(boolean val){
//get Fragment B.
fragmentB.setGlobalButtonState(val);
}
Additional note, if the button meant to be global, it might worth to consider to put the button on the host Activity instead if that suits your context.
Here is the developer guide on communicating with other fragments: https://developer.android.com/training/basics/fragments/communicating .
In the activity have the shared attribute. In each fragment, go to the parent activity to get that attribute on button click.
To reduce dependency, use an interface to obtain the data from the activity, as shown in the link provided
I have Activity in my app with few fragments in it. One of these fragments has a DialogFragment, it called by button click. DialogFragment has 3 buttons - positive, negative and neutral.
public class CompanyNotConnectedToSRDialog extends DialogFragment {
public static final String TAG = CompanyNotConnectedToSRDialog.class.getSimpleName();
private NotConnectedDialogListener mNotConnectedDialogListener;
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity(), R.style.DefaultAlertDialogTheme)
.setTitle("Register")
.setMessage("Do you really want to register?")
.setNeutralButton("Skip", (dialog1, which) -> {
mNotConnectedDialogListener.onSkipBtnNotConnectedDialogPressed();
})
.setNegativeButton("Cancel", null)
.setPositiveButton("Register", (dialog12, which) -> {
mNotConnectedDialogListener.onSendBtnNotConnectedDialogPressed();
})
.create();
}
public interface NotConnectedDialogListener {
void onSkipBtnNotConnectedDialogPressed();
void onSendBtnNotConnectedDialogPressed();
}
public void setListener(NotConnectedDialogListener listener) {
this.mNotConnectedDialogListener = listener;
}
As you can see I created public interface that contains two methods for my skip and register buttons (cancel button listener is null so it doesn't matter) and the Setter for this listener.
Then I implemented this interface in my fragment that calls this dialogFragment, I Overrided methods and called dialogFragment like this:
if (mNotConnectedDialog == null) {
mNotConnectedDialog = new CompanyNotConnectedToSRDialog();
mNotConnectedDialog.setListener(this);
mNotConnectedDialog.show(getActivity().getFragmentManager(), CompanyNotConnectedToSRDialog.TAG);
} else {
mNotConnectedDialog.show(mActivity.getFragmentManager(), CompanyNotConnectedToSRDialog.TAG);
mNotConnectedDialog.setListener(this);
}
The problem is I get NullPointerException if I press the button in my parent Fragment to show DialogFragment, rotate screen and press any button in my DialogFragment, because my listener is null.
java.lang.NullPointerException: Attempt to invoke interface method 'void com.myapp.ui.object.create.dialogs.CompanyNotConnectedToSRDialog$NotConnectedDialogListener.onSendBtnNotConnectedDialogPressed()' on a null object reference
at com.myapp.ui.object.create.dialogs.CompanyNotConnectedToSRDialog.lambda$onCreateDialog$1(CompanyNotConnectedToSRDialog.java:31)
How to handle these clicks and set listeners if this solution is wrong?
PS: please don't tell me about android:configChanges.
So the current solution doesn't work because when you rotate the dialog fragment is destroyed and recreated. So setListener isn't called. The solution depends on if your listener is an activity or another fragment.
If it's an activity you can override onAttach in your DialogFragment and set the listener there. If your listener is a fragment then in your OnCreateDialog method you can look the fragment up by the tag and set the listener that way. For example.
Fragment listenerFragment = getActivity().getSupportFragmentManager().findFragmentByTag(getString(R.string.your_listener_fragment_tag));
if( listenerFragment instanceOf NotConnectedDialogListener ) {
listener = (NotConnectedDialogListener) listenerFragment;
} else {
//Handle what to do if you don't have a listener here. Maybe dismiss the dialog.
}
Yes, after orientation change your listener is null. It's easiest to do the callback to the activity:
public static class DialogFragmentA extends DialogFragment {
// Container Activity must implement this interface
public interface NotConnectedDialogListener {
public void onX();
}
NotConnectedDialogListener mListener;
...
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mListener = (NotConnectedDialogListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement NotConnectedDialogListener");
}
}
...
}
Now you can call mListener.onX etc. in your dialog anywhere, also after orientation change. Your container Activity must implement the interface and will receive the method call.
https://developer.android.com/guide/components/fragments.html#EventCallbacks
I am creating a custom dialog class in which I extend the default Dialog. I am doing some work there. Once the user closes the dialog, how can my activity know that the dialog is closed and time to update the screen views with results?
Do I pass an instance from my activity to the Dialog class so I can call a public method on it? Or is there a better design?
Thank you
what I'll do is this:
Create an Interface, for example: OnDialogCloseListener, with a method called onDialogClose()
The activity have to implement that interface and override the onDialogClose() method
Create an attribute in yout Dialog class of OnDialogCloseListener type and a constructor method and when you create the Dialog pass the activity as a parameter.
OnDialogCloseListener listener;
public MyDialog(OnDialogCloseListener listener) {
this.listener = listener;
}
Now in your onClick method of the close button of your Dialog call the method of the interface. Ex.:
listener.onDialogClose();
And finally in your activity class in the method that you override from OnDialogCloseListener do whatever you want when the Dialog is close.
Note: You can create all the methods that you want in the interface a call each one whatever you want, not only when the dialog is close, you may have other events that you want check when raised.
Hope that can help you
From the docs: http://developer.android.com/guide/topics/ui/dialogs.html
create an interface in your dialog class:
public interface NoticeDialogListener {
public void onDialogPositiveClick(DialogFragment dialog);
public void onDialogNegativeClick(DialogFragment dialog);
}
Register a listener in onAttach() :
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// Verify that the host activity implements the callback interface
try {
// Instantiate the NoticeDialogListener so we can send events to the host
mListener = (NoticeDialogListener) activity;
} catch (ClassCastException e) {
// The activity doesn't implement the interface, throw exception
throw new ClassCastException(activity.toString()
+ " must implement NoticeDialogListener");
}
}
And implement that interface in your calling activity.
A dialog overrides several methods that sense when the dialog is closing. The include
#Override
public void onBackPressed() {
super.onBackPressed();
}
#Override
public void setOnCancelListener(#Nullable OnCancelListener listener) {
super.setOnCancelListener(listener);
}
#Override
protected void onStop() {
super.onStop();
}
These methods all detect that action of closing a dialog. However, the most efficient one that I suggest you use is
#Override
public void onDetachedFromWindow() {
Toast.makeText(getContext().getApplicationContext(), "Dialog had disappeared", Toast.LENGTH_SHORT).show();
super.onDetachedFromWindow();
}
Here is what I would like to do:
1) Inside an Activity a dialog is shown. I use DialogFragment and FragmentManager for this, by calling:
dialogFragment.show(fragmentManager, "edit_task_list");
2) Inside the Dialog I have layout with a custom Button. I would like to perform some action when the button is clicked and later close the dialog.
How should I connect everything? I see two options:
1) onclick attribute in the Button and a method inside the Actvity. That was my original plan, but I don't how to get the Dialog from the Activity to dismiss it. Even if this is not the right way, how could this be done? I would like to understand how this works.
2) set on click listener on the button when the Dialog is created in DialogFragment. This will require me to pass some context from the Activity to the DialogFragment, so I would like to avoid it (and keep the DialogFragment as simple as possible).
Which of those options should I take?
Number 2 Doesn't require you to pass any context (and you shouldn't). You define an interface that can act as a contract between fragments and activities and make your activity implement it.
From your dialog and in your button.onClick(), you do something like this (untested code):
if ( getActivity() != null
&& !getActivity().finishing()
&& getActivity() instanceOf YourInterface) {
((YourInterface)getActivity()).onSomeNiceMethod();
dismiss(); // close the dialog (if this is what you want).
}
The interface looks like:
public interface YourInterface {
void onSomeNiceMethod();
}
And your Activity…
public class YourActivity implements YourInterface {
void onSomeNiceMethod() {
// Hey! The Button In The Dialog Has Been Pressed!
}
}
All Activity and Fragment classes have a built-in callback method for you to use when you start another Activity, Fragment, Dialog, or DialogFragment.
void onActivityResult(int requestCode, int resultCode, Intent data)
Since you want to start the Dialog from an Activity, using the Dialog class is better than the DialogFragment class. The latter is better for starting a dialog from a Fragment, because it has two methods for communicating back to the Fragment (get/set TargetFragment())
The Dialog class has the getOwnerActivity() method. This is the Activity you use when creating the Dialog with one of its constructors.
You set a onClickListener on the button in the Dialog class. To pass the result back to the Activity:
getOwnerActivity().onActivityResult(intIdentifyingme, Activity.RESULT_OK,
intent);
dismiss(); // close the dialog
You put additional info you want to send in an Intent.
1) onclick attribute in the Button and a method inside the Actvity.
That was my original plan, but I don't how to get the Dialog from the
Activity to dismiss it. Even if this is not the right way, how could
this be done? I would like to understand how this works.
Basically your Activity has to remember/know which dialog is active at the moment with something like curDialog=dialogFragment;, then when handling the button onclick action you'll know which dialog to dismiss. But this is really not a good idea since basically the Button View would "leak" from your DialogFragment to your Activity, which breaks object encapsulation.
2) set on click listener on the button when the Dialog is created in
DialogFragment. This will require me to pass some context from the
Activity to the DialogFragment, so I would like to avoid it (and keep
the DialogFragment as simple as possible).
As a previous answer mentioned, you don't need to pass any Context to it, especially since you can get the Activity by calling getActivity().
The solution depends on whether or not this dialog would be used by multiple Activities:
Used by a single Activity: #Martin's solution will work just fine
Used by multiple Activity: abstraction can be used such that only the user's decision is passed to a listener. This is a (modified) solution I came up for the same problem:
public class BaseDialogFragment extends DialogFragment {
protected TextView dialogEn;
protected Button dialogYes;
private Button dialogNo;
protected OnSelectListener listener;
public interface OnSelectListener {
public void onSelect(int type, boolean yes);
}
public void setOnSelectListener(OnSelectListener listener) {
this.listener = listener;
}
public BaseDialogFragment() {
super();
}
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.dialog_confirm, container, false);
dialogYes = (Button) v.findViewById(R.id.yes);
dialogNo = (Button) v.findViewById(R.id.no);
dialogEn = (TextView) view.findViewById(R.id.dialog_en);
dialogEn.setText(getArguments().getString("text_en"));
dialogYes.setOnClickListener(this);
dialogNo.setOnClickListener(this);
return v;
}
public void onClick(View v) {
if (listener != null) {
listener.onSelect(getArguments().getInt("type"),
v == dialogYes ? true : false);
}
getDialog().dismiss();
}
}
To use it some additional info needs to be provided:
Bundle bundle = new Bundle();
bundle.putInt("type", type); //type: an unique integer value that helps differentiate result from different dialogs
bundle.putString("text_en", en); //en: String to be displayed
dialog.setArguments(bundle);
dialog.setOnSelectListener(this);
So if the type value above is set to 115, then a dialogYes button click would trigger public void onSelect(int type, boolean yes) method to be called with 115 and true as the 1st & 2nd parameters.
Your first point about the onClick attribute in the xml should be avoided. Because handling a Dialog that way could be really painfull if you respect events like screen rotation or a setup with multiple dialogs. This leads into leaked window errors most of the time and needs unnecessary code overhead to avoid this. Because you have to keep track of the Dialog which is actually shown yourself.
To be able to dismiss the Dialog this way you can use the Tag you setted as you called dialogFragment.show(fragmentManager, "edit_task_list");
DialogFragment frag = (DialogFragment)getFragmentManager().findFragmentByTag("edit_task_list");
if(frag != null)
frag.dismiss();
The proper solution is to use an interface as a callback for the communication between the DialogFragment and the Activity. This keeps the Dialog modular and the code easy. Here is an example from the docs. For this you don't need a Context. You simply pass the interface to the dialog in the onAttach() callback. It has a reference of the Activity as a parameter, which called that Dialog.
// Example interface for the communication
public interface OnArticleSelectedListener {
public void onButtonClicked(/*any Parameters*/);
}
public static class FragmentA extends DialogFragment {
OnArticleSelectedListener mListener;
...
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mListener = (OnArticleSelectedListener) activity; // get the interface of the Activity
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnArticleSelectedListener");
}
}
...
}
Handle the Button click in the Dialog and call dismiss() in it, that the Dialog can dismiss itself. Have a look at this question why to use dismiss() instead of getDialog().dismiss().
yourButton.setOnClickListener(new OnClickListener(){
#Override
public void onClick(View v){
if(mListener != null) // check if the listener is still valid
mListener.onButtonClicked(...); // calls the Activity implementation of this callback
dismiss(); // dismiss the Dialog
}
});
In onPause() of the Dialog set the reference of the interface to null. This way you can be sure that the callback will only be used if the Dialog is showing.
Your Activity looks something like this to be able to handle the callback:
public class MyActivity extends Activity implements OnArticleSelectedListener{
...
#Override
public void onButtonClicked(...){
// your implementation here
}
}
I don't know your overall setup but if you would use an AlertDialog a click on the Buttons dismiss the Dialog automatically when the method returns.
I have two Fragments in my Activity: fragment A with button X and fragment B with button Y.
How can I change button X's background image when I click button B? Is it possible?
From the documentation,
Because each fragment defines its own layout and its own behavior with its own lifecycle callbacks, you can include one fragment in multiple activities, so you should design for reuse and avoid directly manipulating one fragment from another fragment.
That being said, what you want to do is create event callbacks to the activity. A good way to do that is to define a callback interface inside the fragment and require that the host activity implement it. When the activity receives a callback through the interface, it can share the information with other fragments in the layout as necessary. This is the recommended way to share events between two separate Fragments--that is, sharing the event through the activity.
Check out the link above... it provides a couple nice examples. If you are still having trouble, let me know and maybe I can be more explicit.
Edit #1:
Let's say you click a button in fragment A and you want this to cause changes to a button in fragment B. Here's some sample code illustrating the concept:
The callback interface:
public interface OnButtonClickedListener {
public void onButtonClicked();
}
The activity:
public class SampleActivity extends Activity implements OnButtonClickedListener {
/* Implementation goes here */
public void onButtonClicked() {
// This method is called from fragment A, and when it is called,
// it will send information to fragment B. Remember to first
// check to see if fragment B is non-null.
/* Make call to a method in fragment B that will update its display */
}
}
Fragment A:
public class FragmentA extends Fragment {
OnButtonClickedListener mListener;
/* Implementation goes here */
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mListener = (OnButtonClickedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement OnButtonClickedListener ");
}
}
public void clickButton() {
// When the button is clicked, notify the activity.
// The activity will then pass the information to fragment
// B (if it has been created).
mListener.onButtonClicked();
}
}
Edit #2:
Now, you might be wondering, "Why would anyone ever go through all of this trouble? What's the point of creating a separate activity callback method when you could just have fragment A directly manipulate fragment B?"
The main reason you want to do this is to ensure that each fragment is designed as a modular and reusable activity component. This is especially important because a modular fragment allows you to change your fragment combinations for different screen sizes. When designing your application to support both tablets and handsets, you can reuse your fragments in different layout configurations to optimize the user experience based on the available screen space. For example, on a handset, it might be necessary to separate fragments to provide a single-pane UI when more than one cannot fit within the same activity. Making use of activity callbacks ensures that you will easily be able to reuse your fragments in situations where fragment B is not visible on the screen. For example, if you are on a handheld device and there is not enough room to display fragment B, then you can easily have your activity check to see if fragment B is currently being shown on the screen.
Sorry if this isn't clear... I'm finding it difficult to describe :P. Working your way through this tutorial might help... Activity callbacks make your life especially easier as a developer when you are working with interactive multi-pane layouts.
Base on Alex Lockwood's answer:
The activity:
public class SampleActivity extends Activity{
public interface OnButtonClickedListener {
public void onButtonClicked();
}
private OnButtonClickedListener onButtonClickedListener = null;
public OnButtonClickedListener getOnButtonClickedListener () {
return onButtonClickedListener
}
public void setOnButtonClickedListener (
OnButtonClickedListener onButtonClickedListener {
this.onButtonClickedListener = onButtonClickedListener;
}
}
Fragment A:
public class FragmentA extends Fragment {
private OnButtonClickedListener onButtonClickedListener = null;
private OnClickListener actionBarClickListener = new OnClickListener() {
#Override
public void onClick(View view) {
if (onButtonClickedListener == null){
onButtonClickedListener = ((SampleActivity) getActivity()).onButtonClickedListener ();
}
if (onButtonClickedListener != null) {
onButtonClickedListener
.onButtonClicked();
}
}
};
}
Fragment B:
public class FragmentB extends Fragment {
private OnButtonClickedListener onButtonClickedListener = new OnButtonClickedListener() {
#Override
public void onButtonClicked() {
Toast.makeText(getActivity(), "Button clicked", Toast.LENGTH_SHORT).show();
}
};
#Override
public void onResume() {
super.onResume();
SampleActivity sampleActivity = (SampleActivity) getActivity();
sampleActivity.setSearchBoxTextChangedListener(onButtonClickedListener);
}
}
Hope can help someone.
Setting the onClick attribute for a button in your layout, even your fragment's layout, will call the appropriate method on your Activity.
Your app can then send this signal from your Activity to fragment B.