The basic architecture of my app is that I show a loading screen in the 3 screens of a ViewPager while loading some data(3 fragments for the 3 screens) and when the data has been loaded I show 3 new fragments, replacing the old fragments in all the screens of the ViewPager.
root_frameview.xml (This contains the fragment in each of the screen of the viewpager):-
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/root_view">
</FrameLayout>
Now, in the adapter class of ViewPager, the Fragment getItem(int position) method is :-
#Override
public Fragment getItem(int position) {
Fragment f = new Fragments();
pageFragments.add(f);//pageFragments is an ArrayList keeping track of all the added fragments
return f;
}
And the Fragments class is
public static class Fragments extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.root_frameview, container, false);
FragmentTransaction transaction = getFragmentManager()
.beginTransaction();
transaction.replace(R.id.root_view, new LoadingFragment());//the loading page fragment
transaction.commit();
return rootView;
}
public void replace() {
FragmentTransaction transaction = getFragmentManager()
.beginTransaction();
transaction.replace(R.id.root_view, new LoadedFragment());//the loaded fragment
transaction.commit();
}
}
Note, that it has a replace() method which I call when the data has been loaded to replace the fragment(loading page - LoadingFragment) with the other fragment - LoadedFragment.
Now comes the problem,
This is the loading screen :-
And this is the screen which has been loaded :-
Seems perfectly fine, huh? But the problem starts here:- When I swipe to TAB 2 and come back to TAB 1 , the same loading screen appears as shown in image 1. It does not even loads anything now, just the layout is shown. Where am I going wrong?
Please set setOffscreenPageLimit for the view pager by simply adding this code
pager.setOffscreenPageLimit(3);
For a view page the setOffscreenPageLimit is default is 0 so the next page is loaded . when you move to the tab3 ,tab 1 is killed and it will again called the on createview when you swipe to tab1 . This can be solved in change the setOffscreenPageLimit
Related
I have an Activity with a Layout named "container" where Fragments are inflated in using FragmentTransactions
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE);
transaction.replace(R.id.container, SomeFragment.newInstance());
transaction.addToBackStack(null);
transaction.commit();
One Fragment inflates another fragment with two more Fragments in a TabLayout. This is done with a TabLayout and a ViewPager. Everything works well until I pop it from the backstack.
getSupportFragmentManager().popBackStack();
When I do this, the tabs are recreated and all the lifecycle methods are called. However, they have no content. They layouts seem not to be inflated.
I have a method called initUI(View v) which is called in
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_tabs, container, false);
initUI(rootView);
return rootView;
}
It does the setup for the TabLayout.
private void initUI(View rootView) {
pager = (ViewPager) rootView.findViewById(R.id.viewPagerCollected);
tabLayout = (TabLayout) getActivity().findViewById(R.id.tabs);
pager.setAdapter(new MyPagerAdapter(getActivity().getSupportFragmentManager()));
tabLayout.setupWithViewPager(pager);
tabLayout.setVisibility(View.VISIBLE);
}
Again, this method is called but doesn't seem to do anything when popping the backstack.
What am I doing wrong here? Why is the Fragment not recreated properly even though the onCreateView and initUI are being called?
EDIT: In my TabsFragment, each Fragment has a RecyclerView. When clicking one of the Items, another Fragment opens up, displaying detailed information to the user.
Here the user can navigate back. When doing this, the TabsFragment is visible again but this time without any content. It seems like the inner fragments have not being inflated this time around.
First Add to back stack like this...
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE);
transaction.replace(R.id.container, SomeFragment.newInstance());
transaction.addToBackStack("YOUR_TAG");
transaction.commit();
After than try to popstackback()
I'm trying to create an Android application which contains a single activity with a container and a navigation drawer. The initialy empty container loads fragments which has a ViewPager inside a tab layout in which I load a frgment with a FragmentTransaction:
public static void replaceFragmentInContainer(FragmentManager fragmentManager, Fragment fragmentToShow,
boolean addToBackStack)
{
FragmentTransaction transaction = fragmentManager.beginTransaction();
if (addToBackStack)
{
transaction.addToBackStack(null);
}
transaction.replace(R.id.container, fragmentToShow);
transaction.commit();
}
I'm using v4 fragments with a v7 ActionBar in a v7 ActionBarActivity.
Every loaded fragment is a fragment which only loads tabs with other fragments which they hold the actual usability. An example of such tab loading fragment:
public class MainFragment extends TabsFragment
{
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
super.onCreateView(inflater, container, savedInstanceState);
View contentView = inflater.inflate(R.layout.fragment_main, container, false);
initTabsLayout(savedInstanceState, contentView, R.id.pager);
addTab("tabspectag1", "", R.drawable.draw1, Fragment1.class, null);
addTab("tabspectag2", "", R.drawable.draw2, Fragment2.class, null);
addTab("tabspectag3", "", R.drawable.draw3, Fragment3.class, null);
return contentView;
}
The problem I'm facing is with the backstack. When a fragment is added to the backstack and I press the back button, the app does go back to the previous fragment like I want to and I see the tabs layout itself, but the content of the tabs is empty like there was nothing loaded to that tab. When it happens, I manage to reload the tab's content only when choosing that screen again with the navigational drawer.
I've tried overriding the onBackPressed in the activity:
#Override
public void onBackPressed()
{
if (getFragmentManager().getBackStackEntryCount() > 0)
{
getFragmentManager().popBackStack();
} else
{
super.onBackPressed();
}
}
but that like I said, it's like I'm getting back to the previous fragment but the tab inside that fragment is not repainting the fragment it had.
What can be done to solve this issue?
Since I DO see the tabs layout of the original fragment in the backstack but not the fragment inside the tab, is it possible I just need to somehow refresh the tab content meaning repaint it? If I can do such a thing then how can I do it?
You didn't post the entire code but, bear in mind that when using fragments inside fragments, the outer fragment should use the childfragmentmanager instead of the regular fragment manager.
If you have an Activity, then you have a fragment that has a Viewpager and inside that viewpager the views are fragments, those outer fragments must use their own childfragmentmanager instead of the activities fragmentmanager.
Activity uses getFragmentManager() to instantiate and show new fragments. Fragments use getChildFragmentManager() to instantiate and show new inner fragments. (fragments inside fragments).
If you always use the same fragmentmanager to handle the transactions the behaviour will be unpredictable.
Your Viewpager should have an TabsAdapter associated that extends from FragmentStatePagerAdapter to show new fragments(and uses the getChildFragmentManager from the fragment instead of the activity).
I have ActionBar Tabs setup. It consists of 4 tabs. Everything is fine until I navigate away from TabbedFragment and returning back.
I create tabs like this:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final ActionBar actionBar = getActionBar();
tabs = Lists.newArrayList();
tabs.add(new TabDefinition<>("Tab 1"));
tabs.add(new TabDefinition<>("Tab 2"));
tabs.add(new TabDefinition<>("Tab 3"));
tabs.add(new TabDefinition<>("Tab 4"));
for (TabDefinition tab : tabs) {
actionBar.addTab(actionBar.newTab()
.setText(tab.text)
.setTag(tab.tag)
.setTabListener(this));
}
}
And initialize adapter like this:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.paging_tab_container, container, false);
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
viewPager = (ViewPager) view.findViewById(R.id.pager);
viewPager.setAdapter(new FragmentStatePagerAdapter(getFragmentManager()) {
#Override
public Fragment getItem(int position) {
return tabs.get(position).fragment;
}
#Override
public int getCount() {
return tabs.size();
}
});
viewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
getActionBar().setSelectedNavigationItem(position);
}
});
viewPager.setCurrentItem(getActionBar().getSelectedNavigationIndex(), true);
}
When returning back to TabbedFragment selected tab and 1 next to it would not have any content. Just empty view. But if I select current + 2 fragment content is loaded. And then returning to that first fragment content is reloaded.
For example I have A, B, C, D tabs. Before leaving TabbedFragment I had selected tab A.
When returning to TabbedFragment I still am at tab A, but it's empty. So is tab B.
But when selecting tab C it is created and loaded. Returning to tab A it is recreated.
What could be the problem here?
After a while ran into the same problem again, so updating this question.
If you're using FragmentStatePagerAdapter you should provide FragmentManager via getChildFragmentManager() instead of getFragmentManager(). See Issue 55068: ViewPager doesn't refresh child fragments when back navigation via backstack
Okay so When using a FragmentStatePagerAdapter your fragments will be destroyed when you navigate anymore than one fragment Away since by default offScreenPageLimit is set to 1 by default just as mentioned above.
Typically this Class is used for an activity that has a very large set of Fragments, i.e have to scroll through a large amount of views. If your application does not need more than say 3-4 tabs I would suggest using FragmentPagerAdapter instead, and then specifying your offScreenPageLimit to something like 3, so if you get to the 4th Tab, all 3 tabs before will still be in memory.
Here is some Sample Code for a project on github that i created illustrating how to dynamically load the fragments if you don't want to add this offScreenPageLimit.
https://github.com/lt-tibs1984/InterfaceDemo/blob/master/src/com/divshark/interfacedemo/InterfaceDemoMain.java
Walk through all this code in this Class, and you will see how I'm dynamically loading the fragments, each time my ViewPager is slid over. Most notably at the bottom.
You can download this code, and use it as a test base for what you want to do.
Try adding the setOffScreenPageLimit(2) in the onCreate() method for the viewPager and notice the different behavior. To check the behavior, edit the text in fragment 1. Navigate Away and navigate back, with this set or not. You will see when it is set, the fragment's text remains what you change it to, since the fragment is never recreated.
Please provide additional questions if you have them.
GoodLuck
UPDATE
private static final String [] fragmentClasses = {"com.example.project.YourFragment1","com.example.project.YourFragment2","com.example.project.YourFragment3"};
viewPager.setAdapter(new FragmentStatePagerAdapter(getFragmentManager()) {
#Override
public Fragment getItem(int position) {
Fragment fragmentAtPosition = null;
// $$$$ This is the Important Part $$$$$
// Check to make sure that your array is not null, size is greater than 0 , current position is greater than equal to 0, and position is less than length
if((fragmentClasses != null) && (fragmentClasses.length > 0)&&(position >= 0)&& (position < fragmentClasses.length))
{
// Instantiate the Fragment at the current position of the Adapter
fragmentAtPosition = Fragment.instantiate(getBaseContext(), fragmentClasses[position]);
fragmentAtPosition.setRetainInstance(true);
}
return fragmentAtPosition;
}
#Override
public int getCount() {
return fragmentClasses.length;
}
});
The problem exists in the Fragments you use as tabs, I think. They seem to not show anything when they are resumed (see Fragment lifecycle). The "weird" issue that only the currently selected +/-1 tab is empty, is because the offScreenPageLimit of your ViewPager is 1 by default. All tabs above this threshold are re-created.
Therefore, increasing the value will -- in your case -- cause all your tabs to appear empty after resuming. Check in your Fragment code which lifecycle methods you use to inflate your layout, set adapters and so forth, because that's what's causing your trouble.
I guess this happens because while loading fragment android loads current and current+1, if you debug you would not see onPause getting called for the immediate next fragment.
You can reload content programmatically in onTabChanged() method of TabHost.OnTabChangeListener.
After doing much research, this worked for me.
I have a complex layout with 3 tabs in a fragment, that gets switched out for other fragments. I realized that the ViewpagerAdapter will retain state, even if you press the home button. My problem was switching back and forth would null out the child fragment UI view elements and crash. The key is to not new out your ViewPagerAdapter. Adding the null check for the Adapter worked for me. Also, be sure to allocate setOffscreenPageLimit() for your needs. Also, from what I understand setRetainInstance(true); should not be used for fragments that have UI, it is designed for headless fragments.
In the fragment that holds your Tabs:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_tab, container, false);
tabLayout = (TabLayout) view.findViewById(R.id.tablayout);
viewPager = (ViewPager) view.findViewById(R.id.viewPager);
//Important!!! Do not fire the existing adapter!!
if (viewPagerAdapter == null) {
viewPagerAdapter = new ViewPagerAdapter(getChildFragmentManager());
viewPagerAdapter.addFragments(new AFragment(), "A");
viewPagerAdapter.addFragments(new BFragment(), "B");
viewPagerAdapter.addFragments(new CFragment(), "C");
}
//Allocate retention buffers for three tabs, mandatory
viewPager.setOffscreenPageLimit(3);
tabLayout.setupWithViewPager(viewPager);
viewPager.setAdapter(viewPagerAdapter);
return view;
}
Or more simply when navigating back to tabbedfragment (assuming you use an intent and the fragment is within an activity) use:
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
This keeps the original activity and moves it to the top of the stack rather than recreating it, thus you never need to recreate the viewPager.
I'm making an app that in a certain FragmentActivity has a Tabhost hosting 2 Fragments.
One of them must have either a layout A showing up or a layout B showing up, while the other one has always the same layout.
FragmentActivity
Fragment1 (first tab)
LayoutA
LayoutB
Fragment2 (second tab)
LayoutC
In order to wrap the control of both layouts in separate modules, I made two Fragments, namely FragmentA and FragmentB, that use LayoutA and LayoutB respectively, making the activity look like this:
FragmentActivity
Fragment1 (first tab)
FragmentA
LayoutA
FragmentB
LayoutB
Fragment2 (second tab)
LayoutC
The problem I have is that I cannot make this stable against both:
the user leaving the app when first tab is shown
the user navigates to tab 2 and then back to tab 1
At first my code for Fragment1 looked like this:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = new FrameLayout(getActivity());
view.setId(1);
return view;
}
#Override
public void onStart() {
super.onStart();
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.add(1, mCurrentFragment, "f1");
ft.commit();
}
#Override
public void onStop() {
super.onStop();
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.remove(mCurrentFragment);
ft.commit();
}
Inside onStop I remove the fragment in order to attach it to the new FrameLayout created in OnCreateView when the Fragment restarts next time.
The problem with this code is that apparently I cannot do any transactions when user leaves the app, so it crashes with.
java.lang.RuntimeException: Unable to stop activity {my.package/my.package.MainTabHost}: java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
To solve the problem, I changed the removing of the fragment just before I try to add it to the FragmentTransaction in onStart:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = new FrameLayout(getActivity());
view.setId(1);
return view;
}
#Override
public void onStart() {
super.onStart();
FragmentTransaction ft = getFragmentManager().beginTransaction();
if(getFragmentManager().findFragmentByTag("f1") != null) {
ft.remove(mCurrentFragment);
}
ft.add(1, mCurrentFragment, "f1");
ft.commit();
}
This code has the other problem. Switching from the first tab to the second and then coming back to the first shows a blank layout for Fragment1.
Note: I set all fragments to retain their state.
Solved! I used the second code. I had to this sequence of steps inside onStart:
- detach the fragment
- add it to the new view
- attach it
I'm trying to make a fragment to display tips for the user. The fragment's createView method returns a ViewPager and setup the PagerAdapter through AsyncTask. This fragment is added dinamically in the FragmentActivity when the user press a button. If the user reaches the last item of press the same button again, this fragment is removed from the FragmentActivity. It's important to note that I add the fragment to the FrameLayout idenfified by android.R.id.content.
Ok, the first time I press the button, it works as expected. But the second time I press the button, the ViewPager doesn't appear. When I use the View Hierarchy to see what's happenning, I see the ViewPager in the right place, but with no items. I debugged and noted that the getCount() of the PagerAdapter is called, but the getItem is never called.
This is the createView method of my fragment:
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View tipsView = inflater.inflate(R.layout.tip_manager_layout, container, false);
this.tipPagerAdapter = new TipPagerAdapter(getFragmentManager(), getArguments().getIntArray(TIP_LAYOUT_IDS_KEY));
this.viewPager = (ViewPager) tipsView.findViewById(R.id.tip_pager);
this.viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageSelected(int position) {
if(position == (tipPagerAdapter.getCount()-1)){
stop();
}
}
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
// Workaround to set the PagerAdapter within a fragment transaction
new SetViewPagerAdapterTask().execute();
return tipsView;
}
I add the fragment this way:
FragmentTransaction fragmentTransaction = fragmentActivity.getSupportFragmentManager().beginTransaction();
fragmentTransaction.add(android.R.id.content, this, TAG);
fragmentTransaction.commit();
And remove the fragment this way:
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.remove(tipManagerFragment);
fragmentTransaction.commit();
This is the layout inflated by the fragment:
<android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/tip_pager"
android:layout_width="fill_parent"
android:layout_height="175dp"
android:layout_gravity="center_horizontal|bottom"
android:paddingBottom="2dp" />
Could someone help me with this problem?
EDIT:
For a while, I'm using the show and hide methods of the fragment after adding it to the activity. But it's not the correct approach. I can't understand why I can't add again a fragment with a ViewPager. The ViewPager is added and removed correctly, but the second time, it simply doesn't show anything.
After a long time thinking about my problem, I had the following idea: maybe the problem is related to the fact that I'm removing the fragment that has the ViewPager, but I'm not removing the fragments used by the ViewPager. Then, these fragments continues in the FragmentManager and something strange happens when the new ViewPager tries to use them.
So, I started trying to remove all the fragments. To do this, I created a method in my FragmentPagerAdapter this way:
public void destroy(FragmentTransaction fragmentTransaction){
for(Fragment tipFragment : tipFragments){
fragmentTransaction.remove(tipFragment);
}
}
The adapter is using the tipFragments to create its content. So, I get a fragmentTransaction as parameter and I remove all these fragments. When I want to remove my fragment, I use this code:
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
tipPagerAdapter.destroy(fragmentTransaction);
fragmentTransaction.remove(TipManagerFragment.this);
fragmentTransaction.commit();
This way, I remove all the fragments and when I add the fragment which has the ViewPager again, everything works well.