I have an Activity whose layout is a fragment which can be filled by different Fragments classes. The thing is that when I am in Fragment X and I press back, I would like to override onBackPressed method in order to execute a method asking the user whether to save input data. However, the problem is that onBackPressed can only be overwritten from the activity, then my question is:
Should I create a public method in Fragment X and call it from the overwritten onBackPressed method or should I use interfaces or whatever else?
I already checked other related posts like how to move back to the previous fragment without loosing data with addToBackStack, but I think this is a different question..
When I wanted to do something similar, I created tags for the fragments and a Fragment object in the parent activity named mCurrentFragment. Every time I would load a fragment, I assigned it to mCurrentFragment. So I would override the onbackPressed() from my activity and then check the fragment instances:
#Override
public void onBackPressed() {
if (mCurrentFragment instanceof FragmentA) {
//Do something
//e.g. mCurrentFragment.save();
} else if (mCurrentFragment instanceof FragmentB) {
//Do something else
} else {
super.onBackPressed()
}
}
If you want to call a method from a fragment, you just use the Fragment object you created (in my case mCurrentFragment) and get access to all of its public methods (as in the example for FragmentA above)
§EDITED to include code from the comments
Related
I'm using FragmentStatePagerAdapter, ViewPager.
I'm going to use onSaveInstanceState by overriding to save some states like cursor position of EditText in every fragment.
But when I choose first fragment and next choose second fragment, the onSaveInstanceState of first fragment is not called. If I choose first and next choose third fragment, then the onSaveInstanceState of the first fragment is called.
In this case of choosing first fragment and next second fragment, even the onPause of the first fragment is not called.
What's the reason? How can I solve this problem? I have researched about this problem whole day. But I haven't found solution and correct reason yet.
onSaveInstanceState has cases that it can be called, but how about onPause? Why doens't onPause be called?
I found a solution. I used setUserVisibleHint.
In fragment, I wrote save and restore logic in setUserVisibleHint.
It works well.
This is called when fragment is shown or hidden.
Also I used onViewStateRestored, onSaveInstanceState together for being destroyed cases.
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if(isVisibleToUser) {
if (mInputFrom != null) {
if(isToFocus) {
mInputFrom.requestFocus();
mInputFrom.setSelection(fromCursor);
} else {
mInputOut.requestFocus();
mInputOut.setSelection(toCursor);
}
}
} else {
if (mInputFrom != null) {
fromCursor = mInputFrom.getSelectionStart();
toCursor = mInputOut.getSelectionStart();
}
}
}
setUservisibleHint was deprecated.
So other option is that we can use constructor of FragmentStatePagerAdapter(fm, BEHAVIOR_SET_USER_VISIBLE_HINT);
If we call super's constructor like above in constructor of our customized Adapter that extends FragmentStatePagerAdapter, then onPause, onResume of fragment will be called for every case of being hidden or shown.
First option is suitable for my case I think. So I used first option and has solved perfectly.
I have found many instances of a similar question but no answer unfortunately meets my requirements.
I have many fragment inside an activity and an object that I transmit from one fragment to another. This object will be modified by each fragment I want that when I go back in the backstack I recover the previous fragment instance and the previous version of the object I'am tried with onSaveInstanceState but apparently when backing up in a fragment onSaveInstanceState is not called
override fun onSaveInstanceState(outState: Bundle) {
outState?.run {
putParcelable("PRODUCT",Product)
}
super.onSaveInstanceState(outState)
}
When you modify the fragment, you need to create a new one and .replace the old one.
public BlankFragment modifyFragment() {
int newVar = myVar; // your modified variable. Not the best example
// you need to create a new instance of the fragment
// everytime you modify your fragment so that you can replace the
// one you want to go back to later
BlankFragment blankFragment = BlankFragment.newInstance(++newVar);
getActivity().getSupportFragmentManager().beginTransaction()
.replace(R.id.a_main_fl_root, blankFragment) // When you click back press. It will restore the replaced fragment.
.addToBackStack(null)
.commit();
return blankFragment; // the new fragment is returned so you can replace it again and again.
// what I didn't include is how to get the instance of the replaced fragment when you click back press.
}
There's no need for savedInstanceState because it's only used for when the activity gets destroyed and recreated.
I've been researching this topic but so far no luck. Basically I'm replacing a fragment (A) with another one (B) using FragmentTransaction.replace. In this other fragment (B) I have a 'Cancel' button in the toolbar which when pressed pops back to the previous transaction (A) by calling getActivity().getSupportFragmentManager().popBackStackImmediate().
The problem is I need to update the Activity toolbar to display a different title when I'm showing fragment A and fragment B. I can't seem to find a method which gets called in fragment A whenever I go from A -> B -> A to inform me that it is visible again. The idea is to set the toolbar title in this callback which I cannot seem to find.
Can anyone point me in the right direction please?
Cheers.
Edit:
Method I call to replace the fragment with another one is as follows:
public static void replaceFragment(FragmentActivity parentActivity, int fragmentToReplaceId, Fragment withFragment, Integer enterAnim, Integer exitAnim)
{
FragmentManager fragmentManager;
FragmentTransaction transaction;
fragmentManager = parentActivity.getSupportFragmentManager();
transaction = fragmentManager.beginTransaction();
if ( (null != enterAnim) &&
(null != exitAnim) )
{
transaction.setCustomAnimations(enterAnim, exitAnim);
}
transaction.replace(fragmentToReplaceId, withFragment);
transaction.addToBackStack(null);
transaction.commit();
}
You can inform by overriding method onResume() in fragment and sending the message to activity or changing the Toolbar directly.
#Override
public void onResume() {
super.onResume();
((AppCompatActivity) getActivity()).getSupportActionBar().setTitle("Title");
}
In one activity, when replace A ---> B (A and B both are fragments), can use this call-back:
#Override
public void onAttachFragment(Fragment fragment) {
}
Solved by creating a simple static method in fragment A as follows:
public static void updateActivityTitle(FragmentActivity activity)
{
activity.setTitle(kActivityTitle);
}
Then I'm calling this method in fragment B as follows:
// cancel button has been pressed
private void cancel()
{
INV_CustomersFragment.updateActivityTitle(getActivity());
getActivity().getSupportFragmentManager().popBackStackImmediate();
}
Not ideal but it gets the job done. Any other solution involving a proper callback would be better
Update:
A better solution is the one described by #Rani at Fragment's onResume() not called when popped from backstack. This is more elegant and more maintainable, in fact I implemented this solution in my project. Compared to an equivalent solution for iOS this is still messy if you ask me, still seems the way to go.
Given an Activity that acts as a Home page (it never closes) that launches various fragments, how to know when the Activity is visible to the user?
From what I have observed, when I open a fragment the lifecycle for the Activity never changes, onPause() is not called. And when I close the fragment, onResume() is not called on my Activity.
Here is how I am starting my fragments, I am using this method and passing the fragment I want to launch to it.
public void addFragment(int containerId, Fragment fragment, boolean addToBackStack) {
// Check if the fragment has been added already. If so, then
// don't add the fragment.
Fragment temp = mFragmentManager.findFragmentByTag(fragment.getClass().getName());
if(temp != null && temp.isAdded()) {
return;
}
FragmentTransaction ft = mFragmentManager.beginTransaction();
ft.add(containerId, fragment, fragment.getClass().getName());
if(addToBackStack)
ft.addToBackStack(null);
ft.commit();
}
What is the methodology for indicating that my Activity is visible again? Thanks in advance!
in the oncreate method of your home activity, call
mFragmentManager.addOnBackStackChangedListener(this) ;
and then define
#Override
public void onBackStackChanged() {
int backStackCount = mFragmentManager.getBackStackEntryCount();
if(backStackCount == 0) {} //back to home screen
}
Your Activity is always Visible even if thousand Fragments are showing at the same time, for the sake of understanding Fragments are just Custom-Views, and the Fragment gives a helping hand in handling your View, so onPause() on your activity does not need to called when a Fragment dies or is born,just like inflating a View.
Just like Sir #Tim Mutton said, you need to check your BackStack to know if you are back, or you can use the ViewGroup method ViewGroup.indexOfChild(View child) - this method will an int of value getChildCount()-1 which means its on top of its fellow sibblings..
Hope it helps
I am using a ViewPager with 4 pages, and I'm looking for an efficient way to replace/switch between fragments in each page.
This is the interaction pattern that I'm trying to create:
User presses a button on a page that currently holds fragment A
Fragment A is swapped out for some new fragment B
The user does some work in fragment B, and then presses a button when he/she is done
Fragment B is removed, and is replaced by fragment A (the original fragment)
I've found a way to do this, but it seems to have significant flaws. The solution involves removing the original fragment, and then overriding getItemPosition (essentially the method described in this related question):
//An array to keep track of the currently visible fragment in each page
private final Fragment[] activeFragments= new Fragment[4];
public void openFragmentB(ViewPager pager, int position) {
startUpdate(pager);
//Remove the original fragment
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.remove(activeFragments[position]);
transaction.commit();
//Create a new tile search fragment to replace the original fragment
activeFragments[position] = FragmentB.newInstance();
pageStates[position] = PageState.STATE_B;
finishUpdate(pager);
notifyDataSetChanged();
}
#Override
public int getItemPosition(Object object) {
//If the main fragment is not active, return POSITION_NONE
if(object instanceof FragmentA) {
FragmentA a = (FragmentA) object;
if(pageStates[a.getPosition()] != PageState.STATE_A) {
return POSITION_NONE;
}
}
//If the secondary fragment is not active, return POSITION_NONE
if(object instanceof FragmentB) {
FragmentB b = (FragmentB) object;
if(pageStates[b.getPosition()] != PageState.STATE_B) {
return POSITION_NONE;
}
}
return POSITION_UNCHANGED;
}
This method works, but has undesirable side effects. Removing the fragment and setting it's position to POSITION_NONE causes the fragment to be destroyed. So when the user finishes using FragmentB, I would need to create a new instance of FragmentA instead of reusing the original fragment. The main fragments in the pager (FragmentA in this example) will contain relatively large database backed lists, so I want to avoid recreating them if possible.
Essentially I just want to keep references to my 4 main fragments and swap them in and out of pages without having to recreate them every time. Any ideas?
A simple way to avoid recreating your Fragments is to keep them as member variables in your Activity. I do this anyway in conjunction with onRetainCustomNonConfigurationInstance() order to retain my fragments during configuration changes (mostly screen rotation). I keep my Fragments in a 'retainer' object since onRetainCustomNonConfigurationInstance only returns a single object.
In your case, instead of calling Fragment.newInstance() all the time, just check to see if the fragments contained in the retainer object is null before creating a new one. If it isn't null, just re-use the previous instance. This checking should happen in your ViewPager adapter's getItem(int) method.
In effect, doing this basically means you are handling whether or not Fragments are recycled when getItem is called, and overriding the getItemPosition(Object) method to always return POSITION_NONE when for relevant Segments.
FragmentPagerAdapter provides an overrideable method called getItemId that will help you here.
If you assign a unique long value to each Fragment in your collection, and return that in this method, it will force the ViewPager to reload a page when it notices the id has changed.
Better late than never, I hope this helps somebody out there!