Fragment and DialogFragment Lifecycle Relationship - android

I have Fragment "A" where I have an ImageButton in place. Upon clicking this button a DialogFragment "B" is called to the foreground where Fragment "A" is partially visible in the background. DialogFragment "B" presents the user with a list of choices. Upon clicking a specific choice DialogFragment "B" is dismissed via Dismiss() and Fragment "A" becomes fully visible again.
During this action I need to update the ImageButton on Fragment "A" to represent the user's choice made on DialogFragment "B" (basically a new image for the ImageButton).
Am I correct in thinking the right place to update the ImageButton on Fragment "A" is during OnResume? Does Fragment "A" go into OnPause while FragmentDialog "B" is being shown? Therefore upon returning from DialogFragment "B", Fragment "A" would trigger its OnResume and that's where I should make the update changes to the ImageButton being presented to the user?
I hope my explanation is clear. Any detailed help on where and how I should be updating the ImageButton would be highly appreciated.

With the addition of ViewModels and LiveData solving this problem just got easier. Create a viewModel which both fragments reference. Put the next line in the OnCreate of the fragments. Can also be in onCreateDialog of the dialogfragment.
myViewModel = ViewModelProviders.of(getActivity()).get(MyViewModel.class);
When the dialog is dismissed, call a method on myViewModel, which updates a LiveData variable:
dialogBuilder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialogInterface, int i) {
myViewModel.setButtonPressed(PositiveButtonPressed);
}
});
dialogBuilder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialogInterface, int i) {
myViewModel.setButtonPressed(NegativeButtonPressed)
}
});
In the viewModel the method sets a MutuableLiveData variable for example to the image to be shown.
void SetButtonPressed(int buttonPressed){
if (buttonPressed==positiveButtonPressed){
imageToBeShown.setValue(image A);
}
else{
imageToBeShown.setValue(image B);
}
}
Set an observer to LiveData variable in onActivityCreated:
myViewModel.imageToBeShown().observe(getViewLifecycleOwner(), new Observer<Image>() {
#Override
public void onChanged(#Nullable Image image) {
button.setBackground(image);
}
}
});
Of course you can implement a getter method and keep the MutuableLiveData variable private. The observer then just obeserves the getter methode.

I had same problem when tried with Interface-Callback method but OnResume of Fragment didn't got triggered when DialogFragment was dismissed since we are not switching to other activity.
So here Event Bus made life easy. Event Bus is the easiest and best way to make communication between activities and fragments with only three step, you can see it here
This is nothing but publish/subscribe event bus mechanism. You will get proper documentation here
Add EventBus dependency to your gradle file -
compile 'org.greenrobot:eventbus:x.x.x'
OR
compile 'org.greenrobot:eventbus:3.1.1' (Specific version)
For the above scenario -
Create one custom POJO class for user events -
public class UserEvent {
public final int userId;
public UserEvent(int userId) {
this.userId = userId;
}
}
Subscribe an event in Fragment A whenever it is posted/published from DialogFragment or from somewhere else -
#Subscribe(threadMode = ThreadMode.MAIN)
public void onUserEvent(UserEvent event) {
// Do something with userId
Toast.makeText(getActivity(), event.userId, Toast.LENGTH_SHORT).show();
}
Register or Unregister your EventBus from your Fragment A's lifecycle especially in onStart and onStop respectively -
#Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
#Override
public void onStop() {
EventBus.getDefault().unregister(this);
super.onStop();
}
In the end, on clicking specific item, Publish/Post your event from DialogFragment -
EventBus.getDefault().post(new MessageEvent(user.getId()));

Fragment A won't go into onPause so onResume won't get called
http://developer.android.com/guide/components/fragments.html
Resumed
The fragment is visible in the running activity.
Paused
Another activity is in the foreground and has focus, but the activity
in which this fragment lives is still visible (the foreground activity
is partially transparent or doesn't cover the entire screen).

Related

Updating Activity with OnResume after Fragment Closes

I have a fragment that adds data to my database. When I close it with dismiss(), it returns to my activity. I want to then update my recyclerView in that activity.
My understanding of the activity lifecycle is that onResume should be called correct? I have a Log in my onResume method and as far as I can tell it is not being called.
What is the better solution, and why is this not being called?
onResume
#Override
public void onResume() {
super.onResume();
Log.e("Resume", "Resuming");
}
The button click listener in my fragment. The Log here works perfectly fine.
//save button for saving workouts
mButton2.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
String title = mEditText.getText().toString();
String category = mSpinner.getSelectedItem().toString();
String weight = mEditText2.getText().toString();
int icon = buildIcon(category);
//pass all the data into a new object for the Recycler View to store and show also refresh
db.addWorkout(new Workout(title, weight, "8/8/8", icon));
Log.e("Database Build", "Added a new workout: " + title);
dismiss();
}
});
The Activity was never paused when you started dealing with the fragment. onResume won't get called in that scenario and that's expected lifecycle behavior.
You should consider implementing some type of callback to let the Activity know when the Fragment has closed. The android documentation has a really good explanation of how to communicate with fragments. Use the pattern in the documentation and build yourself an OnFragmentClosedListener

Android: Callback to fragment from DialogFragment?

I know to pass back something from a fragment to its calling activity you can use onAttach which has the "activity" parameter. You can set the activity to variable and call an interface on it later. So passing data from the fragment back to the activity. All great.
I would like to do the same thing but this time i have a standard fragment and I want to call a DialogFragment and then have the DialogFragment call back to the original fragment but I can't use onAttach is wants a Activity.
Anyone know the best way of doing this ?
Thanks
Obviously you could just make things public in your activity and set them from your fragment. But then you have to keep references to your activity, and possibly have unwanted public variables and/or setters.
You could use EventBus and you would not need any of that.
In your activity you need to register an event
#Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
#Override
public void onStop() {
EventBus.getDefault().unregister(this);
super.onStop();
}
// This method will be called when a MessageEvent is posted
public void onEvent(MessageEvent event){
Toast.makeText(getActivity(), event.message, Toast.LENGTH_SHORT).show();
}
Then you can simply call your event from your fragment or anywhere you like,
EventBus.getDefault().post(new MessageEvent("Hello everyone!"));
Some more information on EventBus can be found here
And another possibly useful tutorial.
One way to contact another fragment from there is to access it in the implemented method in your Activity:
//In your Activity...
#Override
public void callbackFromFragmentA(){
FragmentB fragment = (FragmentB) getFragmentManager.findFragmentById(android.R.id.content);
if (fragment != null) {
fragment.callFragmentMethod();
}
}

Clear password field in other activity on exit

I need to clear password field on exit of app,i am exiting the app in other activity and on exit it goes to mainActivity which has login details in which i need to clear password field,how will i do this in other activity ,i tried using setText("") but in vain.
public void backButtonHandler() {
AlertDialog.Builder alertDialog = new AlertDialog.Builder(
ReminderActivity.this);
// Setting Dialog Title
alertDialog.setTitle("Leave application?");
// Setting Dialog Message
alertDialog.setMessage("Are you sure you want to leave the application?");
// Setting Positive "Yes" Button
alertDialog.setPositiveButton("YES",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
//I need to clear here all pwd data present in MainActivity in edittext
finish();
}
});
// Setting Negative "NO" Button
alertDialog.setNegativeButton("NO",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// Write your code here to invoke NO event
dialog.cancel();
}
});
// Showing Alert Message
alertDialog.show();
}
In your MainActivity do like this:
#Override
public void onResume() {
super.onResume(); // Always call the superclass method first
editText.SetText("");
}
So, it will clear editText value, whenever your MainActivity will get resumed.
Or when you click the login button do
editText.SetText("");
Then it will be cleared before another activity starts
Using setText won't work and may even throw some type of NPE if your activity is no longer in memory.
There are a couple of ways you can do this depending on your requirements:
You can simply clear the text field in the activity/fragment's onStop or onDestroy overrides
If you absolutely have to set the text field from a different activity, then I suggest the following design:
In your Application class, create a nested class that implements ActivityLifeCycleCallbacks. This is a great class to implement as it allows you to monitor the state of all of your activities. It resolved a lot of problems for me
If you have a singleton model, or static String value that binds to your password TextView then you're almost there (other workarounds available if you give us more info).
So let's look at some code:
Design 1:
public void onStop()
{
super.onStop();
myTextField.setText("");
}
Your Application class:
public class MyApplication extends Application
{
...
private static final class MyActivityLifeCycleCallback implements ActivityLifecycleCallbacks
{
#Override
public void onActivityStopped(Activity activity)
{
//Here, I am assuming that your class name is MainActivity
Log.i(TAG, "onActivityStopped:" + activity.getLocalClassName());
if("MainActivity".isEqual(activity.getLocalClassName())
{
myDataModel.getInstance().setPassword("");
//or if your password String member is static
//((MainActivity)activity).myPasswordMember = "";
}
//Or if you want to only clear the password text when RAActivity stops, simply replace "MainActivity" with the RAActivity class name.
}
#Override
public void onActivityDestroyed(Activity activity,
Bundle savedInstanceState)
{
Log.i(TAG, "onActivityDestroyed:" + activity.getLocalClassName());
if(activity.getLocalClassName().contains("MySecondActivity"))
{
//reset your password here - implementation will depend on how you have your data model organized (private, singleton, static, etc.)
}
}
....
}
Remember that this design would work well with some type of a singleton or central data model (think MVC architecture) so that you can propagate the change in data to your components.
Hope it helps
EDIT:
I have added the code according to your comment. But to be honest, I think it's a better idea to simply call the setText("") method in the MainActivity's onStop function like I suggested. This is a simple problem and my second design might be a bit too much. Anyway, the code is updated, so if you like it, mark it as an answer :)
Here's another idea. IF RAActivity calls MainActivity again (probably not) using startActivity, you can simply pass a bundle value to MainActivity that tells it that it's coming from RAActivity. That way, MainActivity can clear the password if it was called from RAActivity. Lots of options.

Android: What is the proper way of connecting dialog buttons with an activity?

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.

how to access button of fragment A from fragment B

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.

Categories

Resources