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).
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()
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
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.
My question looks similar to this. However, it is a bit more in general:
Let me try to explain: I am using the ActionBarSherlock with the Tabsadapter and Viewpager. I have replaced all the fragments with my own and now I have a button in one of my fragments which, when clicked on, starts the following onclick handler:
public void onAcceptSelected() {
SherlockFragment addFirstChildFragment = AddChildFragment.newInstance();
getSupportActionBar().setTitle("The new title of the fragment here");
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.pager, addFirstChildFragment);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.addToBackStack(null);
ft.commit();
}
The problem is then that the following method in the new fragment:
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_childeditor, container, false);
return v;
}
does not result in actually providing a new visible layout: the tab its contents are empty (e.g. I see a holo white themed empty tab).
Now I have the following questions:
My main question is: "How should I replace fragments within a tab?"
with subquestions:
Should I adapt the XML of the Tabs and Pager example and fit it with fragment containers?
What is the best practice for using tabs and a viewpager with many fragments? Because I want to use the tabs as my main navigation, but I will have plenty of different fragments.
Its fairly simple. Just change the fragment associated to the tab like the way you replace other fragment.
Fragment donorpage = new DonorRegistrationPage(); // new fragment for the tab
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(R.id.fragment_container, donorpage, "frag");
transaction.addToBackStack(null);
// Commit the transaction
transaction.commit();
This should replace the fragment of the tab.
The answer resides in the answers to this question
Actionbarsherlock + tabs + multi fragments?
Answers to my questions are:
1. No this is not necessary: I can add them programaticllay.
2. I can use fragmentstacks as the example shows in the link above.
I'm working with Fragments for the first time and running into a weird behavior. I have an activity with a Fragment covering the entire view. It contains a ListView. When I tap on an item in the ListView, I want to show a details fragment which contains information about that item.
This is how I'm presenting the new fragment view:
FragmentManager fragmentManager = this.getActivity().getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
DetailsFragment fragment = new DetailsFragment();
transaction.add(R.id.viewRoot, fragment).setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.addToBackStack("Details").commit();
R.id.viewRoot is the id of the root layout in the first Fragment's layout xml, so the new Fragment fills the entire screen.
My Problem: When the new fragment is visible and covering the entire screen, any clicks or taps which land on the background (i.e. not hitting a button or textfield on the new fragment) appear to be going through the view and landing on the first fragment. So, I tap on a ListView item, which causes the DetailsFragment to get added to the screen. Then when I tap on the background of the DetailsFragment, that tap falls through and lands on the ListView causing another DetailsFragment to be added for whatever item I happened to hit behind the scenes.
Am I adding my new fragment incorrectly? Why are my clicks/taps falling through?
EDIT for caiuspb:
public class BaseFragment extends Fragment {
public void pushNewFragment(Fragment fragment, String description) {
FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.remove(this);
transaction.add(R.id.viewRoot, fragment).setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.addToBackStack(description).commit();
}
}
All of my fragments extend my BaseFragment class.
I experimented with the suggestions I received, but none of them worked. In the end, my solution was to add an OnTouchListener to the root view of my subfragments which discarded all touches.
View view = inflater.inflate(R.layout.mylayout, container, false);
view.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
return true;
}
});
There is another post here on StackOverflow about that strange Fragment behaviour. Try to remove your Fragment before you add it because the replace Method seems to be buggy
edit:
Move the Fragment logic into your Activity because your activity contains your Fragments. Today I tried to use the FragmentActivity and Fragments from the Android Support Library. Now I can use the replace method withouht any bugs. This is my approach and this works:
public class DashActivity extends FragmentActivity{
...
private void changeRightFragment(Fragment fragment) {
Log.i(TAG, "Loading Fragment into container: " + fragment.getClass().toString());
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.rightfrag, fragment);
// Add a transition effect
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
// Add the Fragment to the BackStack so it can be restored by
// pressing the Back button
ft.addToBackStack(null);
ft.commit();
}
...
public void setUserFragment(User user) {
UserFragment fragment = new UserFragment(user);
changeRightFragment(fragment);
}
So if you want to invoke a changeFragment method from inside your Fragment use a callback mechanism to the Activity