The background
I have single activity in my App which loads 2 fragments based on some menu item selection
public class ActivityMain extends AppCompatActivity{
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState == null) {
loadFragment(1); // DEFAULT FRAGMENT, AT THE BEGINNING
}
}
..........................
..........................
// This method is called above, ALSO onItemClick in the Navigation Drawer (code not included for brevity)
public void loadFragment(int position) {
Fragment fragment = null;
switch (position) {
case 1:
fragment = new Fragment1();
break;
case 2:
fragment = new Fragment2();
break;
default:
break;
}
if (fragment != null) {
getFragmentManager().beginTransaction().replace(R.id.fragment_container, fragment, "frag_" + position).addToBackStack(null).commit();
}
}
}
"Fragment1" is a simple fragment with a fixed text in a TextView. "Fragment2" uses SlidingTabLayout to load a Fragment "FragmentViewPager" in a viewpager using FragmentStatePagerAdapter.
The issue I am facing:
Even if I remove "Fragment2" from the Activity's Frame Layout (using getFragmentManager().beginTransaction().remove), Fragment "FragmentViewPager" does not get destroyed, rather it resumes every time Activity resumes.
Question
Why FragmentViewPager is not destroyed with "Fragment2"?
If you are using FragmentStatePagerAdapter then this will not destroy fragment and when you swipe and come back to that fragment it will show old data without refresh. Because of not destroyed by viewpager.
Related
How can I hide SearchFragment and display ChatFragment, without remove() in ViewPager? I had no issues with creating new instance everytime, but I don't want onDestroy() to have place, since i will be switching these 2 views very frequently. I want to create them once and have the ability to show/hide one for another.
My code is making two views to be seen in fragment_search(id cointainer)at once, then just crashes.
I was thinking about creating both Fragments at start, and then just manipulate them by attach and detach, but I can't really figure this out since ViewPager doesn't make it easier
public Fragment getItem(int position) {
switch (position) {
case 0:
if (fragment1 == null)
{
fragment1 = SearchFragment.newInstance(new SearchFragment.MyListener() {
#Override
public void onChatFound() {
// Dont want to remove fragment, just hide this view || fm.beginTransaction().remove(fragment1).commit();
Fragment chatTag = fm.findFragmentByTag("ChatTag");
if (chatTag == null)
{
chatFragment = ChatFragment.newInstance();
fm.beginTransaction().add(R.id.fragment_search, chatFragment, "ChatTag").commit();
}
fragment1 = chatTag;
notifyDataSetChanged();
}
});
}
return fragment1;
I want to use 3 fragments within an Android App, I red:
Creating-and-Using-Fragments
.But I want to use the viewpager for swiping and displaying the fragments like explained in:
ViewPager-with-FragmentPagerAdapter
.But this code or the default code of Android Studio Example, use newInstance to create the instances, each time it is needed and destroy when not needed.
// Returns the fragment to display for that page
#Override
public Fragment getItem(int position) {
switch (position) {
case 0: // Fragment # 0 - This will show FirstFragment
return FirstFragment.newInstance(0, "Page # 1");
case 1: // Fragment # 0 - This will show FirstFragment different title
return FirstFragment.newInstance(1, "Page # 2");
case 2: // Fragment # 1 - This will show SecondFragment
return SecondFragment.newInstance(2, "Page # 3");
default:
return null;
}
}
but I want to create once for all :
// Within an activity
private FragmentA fragmentA;
private FragmentB fragmentB;
private FragmentC fragmentC;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
fragmentA = FragmentA.newInstance("foo");
fragmentB = FragmentB.newInstance("bar");
fragmentC = FragmentC.newInstance("baz");
}
}
and only hide/show them, as in the example:
// ...onCreate stays the same
// Replace the switch method
protected void displayFragmentA() {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
if (fragmentA.isAdded()) { // if the fragment is already in container
ft.show(fragmentA);
} else { // fragment needs to be added to frame container
ft.add(R.id.flContainer, fragmentA, "A");
}
// Hide fragment B
if (fragmentB.isAdded()) { ft.hide(fragmentB); }
// Hide fragment C
if (fragmentC.isAdded()) { ft.hide(fragmentC); }
// Commit changes
ft.commit();
}
But how to do that with a FragmentPagerAdapter
public Fragment getItem(int position) no longer has to be like that
Also, how to access a data
public double [][] tableaux;
that is in the MainActivity from one Fragment.
Data will be persistant if I assign a pointer of the Fragment I just created in the MainActivity onCreate to point on the MainActivity.tableaux
You can return you pre-initialized fragments in getItem method.
#Override
public Fragment getItem(int position) {
switch (position) {
case 0: return fragmentA;
case 1: return fragmentB;
case 2: return fragmentC;
default: return null;
}
UPDATE: #Alok is right. You should not have fragment references in Activity. And I suggest instead of increasing offscreen page limit using setOffscreenPageLimit, you should consider saving & restoring fragment states using public void onSaveInstanceState(Bundle) and savedInstanceState argument of public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState).
#Lotfi you don't need to save your fragment inside activity, it is prone to leaks .
Also you can assign id to each of your fragment and later when you need to reuse that fragment you can ask fragment manager to return the fragment by passing ie. by calling fragment manager method findFragmentByID() inside your getItem().
I have an Android activity that holds and manages six fragments, is fragment is a step in a flow, some of the fragments are replaced and some of them are added.
The Activity just uses a Framelayout as the container for the fragments as follows:
<FrameLayout
android:id="#+id/content"
android:layout_below="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent" />
Then the flow of the fragments is like this:
//Activity starts, add first Fragment
fragmentManager.beginTransaction().replace(R.id.content, FirstFragment.newInstance(listOfItems)).commit();
then
//User pressed button, activity got callback from first fragment
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.content, fragment2);
transaction.addToBackStack("frag2");
transaction.commit();
then
//Another callback from Frag2, perform the add of frag 3
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.add(R.id.content, fragment3);
transaction.addToBackStack("frag3");
transaction.commit();
And so on....
I also manage the back stack from the Activity like this:
//Controlling the back stack when the user selects the soft back button in the toolbar
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
if (fragmentManager.getBackStackEntryCount() == 0) {
super.onBackPressed();
overridePendingTransition(R.anim.no_change, R.anim.slide_down);
} else {
if(!BaseFragment.handleBackPressed(getSupportFragmentManager())){
super.onBackPressed();
Fragment fragment = fragmentManager.getFragments()
.get(fragmentManager.getBackStackEntryCount());
fragment.onResume(); //Make sure the fragment that is currently at the top of the stack calls its onResume method
}
}
return true;
}
return super.onOptionsItemSelected(item);
}
//Controlling the back stack when the user selects the "hardware" back button
#Override
public void onBackPressed() {
if (fragmentManager.getBackStackEntryCount() == 0) {
super.onBackPressed();
overridePendingTransition(R.anim.no_change, R.anim.slide_down);
} else {
if(!BaseFragment.handleBackPressed(getSupportFragmentManager())){
super.onBackPressed();
Fragment fragment = fragmentManager.getFragments()
.get(fragmentManager.getBackStackEntryCount());
fragment.onResume(); //Make sure the fragment that is currently at the top of the stack calls its onResume method
}
}
}
My problem is that I open the app and go to this Activity which loads the fragments and then go through the flow to a certain stage ( I haven't narrowed it down yet) then I press the home button and blank my screen. Now after a certain amount of time when I open the app again it opens on the fragment I left but everything seems to be messed up, when I press back it seems to pop the wrong fragment and the UI becomes mixed up with the different fragments.
My guess is that when I open the app again the Activity onResume or the Fragment onResume or some lifecycle event is being called that I am not handling correctly?
So I was wondering is there best practices, guidelines or patterns that should be adhered to when using a Fragment pattern like I am doing so?
Since you have so many fragments in one activity, and they use the same container, that means all fragments are in the same place, and only one fragment will show at a time.
So why don't you use ViewPager and let FragmentPagerAdapter manager these fragments? In this way, you do not need to manager fragment lifecycle by yourself, you just need to override FragmentPagerAdapter methods:
to create fragment instance by getItem,
to update fragment by getItemPosition and Adapter.notifyDataSetChanged(),
to show selected fragment by mViewPager.setCurrentItem(i)
Code snippets, detail refer to https://github.com/li2/Update_Replace_Fragment_In_ViewPager/
private FragmentPagerAdapter mViewPagerAdapter = new FragmentPagerAdapter(getSupportFragmentManager()) {
#Override
public int getCount() {
return PAGE_COUNT;
}
// Return the Fragment associated with a specified position.
#Override
public Fragment getItem(int position) {
Log.d(TAG, "getItem(" + position + ")");
if (position == 0) {
return Page0Fragment.newInstance(mDate);
} else if (position == 1) {
return Page1Fragment.newInstance(mContent);
}
return null;
}
#Override
// To update fragment in ViewPager, we should override getItemPosition() method,
// in this method, we call the fragment's public updating method.
public int getItemPosition(Object object) {
Log.d(TAG, "getItemPosition(" + object.getClass().getSimpleName() + ")");
if (object instanceof Page0Fragment) {
((Page0Fragment) object).updateDate(mDate);
} else if (object instanceof Page1Fragment) {
((Page1Fragment) object).updateContent(mContent);
}
return super.getItemPosition(object);
};
};
What I need is exactly an onResume method (as it works for activities) for a specific fragment. I'm adding the fragment (let's say fragment A) to the back stack, and opening another fragment (fragment B) (again adding to back stack) from fragment A. I want to update toolbar when fragment B is closed and fragment A is on screen again. I expect onCreateView to get called but it's not getting called when I pop fragment B. I also tried adding an OnBackStackChangedListener to fragment A but then I cannot track which fragment is on the screen when the back stack changes.
So my question is how to make onCreateView get called when I turn back to fragment A. And if this is not a good practice, how else can I track this event?
Edit
I'm showing new fragments with this code:
getSupportFragmentManager().beginTransaction()
.add(R.id.content, fragment)
.addToBackStack(tag)
.commit();
Should I change it somehow to make onCreateView get called? Since I'm adding new fragment B on existing fragment A (I can even click on a button which is in fragment A when B is on the screen), when I pop fragment B, nothing changes with fragment A's situation.
Override this method in the Fragment and check the boolean value
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
//Log.e("setUserVisibleHint", "isVisibleToUser " + isVisibleToUser);
}
Put the code that you need to be executed whenever the fragment becomes visible/is hidden in this method, according to the isVisibleToUser boolean value
Did you try OnBackStackChangedListener this way?
public class BlankFragment2 extends Fragment {
public BlankFragment2() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
getFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
#Override
public void onBackStackChanged() {
if(getFragmentManager()==null)
return;
Fragment fr = getFragmentManager().findFragmentById(R.id.container)//id of your container;
if (fr instanceof BlankFragment2) {
//On resume code goes here
}
}
});
return inflater.inflate(R.layout.fragment_blank_fragment2, container, false);
}
}
I hope this solution will works.
1) Put/call addOnBackStackChangedListener on your Activity
getSupportFragmentManager().addOnBackStackChangedListener(backStacklistener);
2) Define backStacklistener inside your Activity
FragmentManager.OnBackStackChangedListener backStacklistener = new FragmentManager.OnBackStackChangedListener() {
public void onBackStackChanged() {
FragmentManager manager = getSupportFragmentManager();
if (manager != null) {
Fragment fragment = manager.findFragmentById(R.id.fragment);
if(fragment instanceof OutboxFragment) {
OutboxFragment currFrag = (OutboxFragment) fragment;
currFrag.onFragmentResume();
}
}
}
};
3) Provide a method on your fragment that you want to be triggered. In this case I create method named onFragmentResume()
public void onFragmentResume() {
MainActivity activity = (MainActivity) getActivity();
activity.showFab();
// or do another thing here
}
Good luck!
I'm new to Android, and looks like there are still some things missing in my understanding of the activity lifecycle. I have a tabbed application created with a FragmentActivity. A custom ViewPager is managing the presentation of tabs and in one of those tabs I want a simple drill-down with the ability to use the hardware "Back" button to go back up a level to the previous view.
I implemented this drill-down by just substituting an appropriate Fragment into the tab by checking the value of a flag (which determines where in the drill down we are):
public class SectionsPagerAdapter extends FragmentStatePagerAdapter {
#Override
public Fragment getItem(int position) {
Fragment fragment;
switch (position) {
case 0 :
if (stateFlag == 0) fragment = (Fragment) new InitialFragment();
else fragment = (Fragment) new FinalResultsFragment();
break;
case 1:
fragment = (Fragment) new SecondTabFragment();
break;
default:
fragment = (Fragment) new ThirdTabFragment();
break;
}
return fragment;
}
To go back to the "previous" Fragment I do this:
#Override
public void onBackPressed() {
int p = mViewPager.getCurrentItem();
switch (p) {
case 0:
if (stateFlag == 0)) {
super.onBackPressed();
} else {
stateFlag = 0;
mViewPager.getAdapter().notifyDataSetChanged();
mViewPager.setCurrentItem(0);
}
break;
default:
super.onBackPressed();
break;
}
}
This works fine, until I try changing the orientation. The Fragments come back from that change successfully, but when I try pressing the Back button while in the FinalResultsFragment state, I get
IllegalStateException: Fragment already active
E/AndroidRuntime(1094): at android.support.v4.view.PagerAdapter.notifyDataSetChanged(PagerAdapter.java:276)
Why is the change in orientation breaking this particular part of the code?
Edit:
So if I open the app, first tab active, I press a button that loads a different fragment in that tab, then press the Back button - it's all good and the first fragment is loaded, as expected.
But if I open the app, first tab active, I press a button that loads a different fragment in that tab, the rotate the screen, the fragment is re-loaded just fine, then press the Back button - crash!
Edit:
here is the complete error log: http://pastebin.com/ESDTstsm