First: Sorry for the wall of text/code, but I think most of it is needed to understand the problem.
I am creating an app using Fragments
in a ViewPager
and a TabHost
. The ViewPager
has a custom FragmentPagerAdapter
that will feed the various pages in the ViewPager
. I have ran into a problem where the custom FragmentPagerAdapter
starts adding the various Fragments
to the BackStack
, but it fails at a point where it checks that the container ID (in this case the ID of the ViewPager
) against the ID of the Fragments
to add. These are different, thus the program fails. I am fairly new to using Fragments
, so I am not sure if my code follows best practice. What could be the error in the following?
The Activity, which inflates the main XML layout.
public class MyActivity extends FragmentActivity implements TabHost.OnTabChangeListener, ViewPager.OnPageChangeListener{
private MyViewPager mViewPager;
private FragmentTabHost mFragmentTabHost;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.my_activity);
setupViewPager();
setupFragmentTabHost();
}
private void setupViewPager()
{
mViewPager = (MyViewPager) findViewById(R.id.my_pager);
}
private void setupFragmentTabHost()
{
mFragmentTabHost = (FragmentTabHost) findViewById(android.R.id.tabhost);
mFragmentTabHost.setOnTabChangedListener(this);
mFragmentTabHost.setup(this, getSupportFragmentManager(), android.R.id.tabcontent);
mFragmentTabHost.addTab(mFragmentTabHost.newTabSpec("tab1").setIndicator("Tab 1", null), TabFragment.class, null);
mFragmentTabHost.addTab(mFragmentTabHost.newTabSpec("tab2").setIndicator("Tab 2", null), TabFragment.class, null);
mFragmentTabHost.addTab(mFragmentTabHost.newTabSpec("tab3").setIndicator("Tab 3", null), TabFragment.class, null);
}
@Override
protected void onDestroy()
{
}
public MyViewPager getMyPager()
{
return mViewPager;
}
@Override
public void onTabChanged(String tabId) {
int position = mFragmentTabHost.getCurrentTab();
mViewPager.setCurrentItem(position);
}
@Override
public void onPageSelected(int position)
{
mFragmentTabHost.setCurrentTab(position);
}
@Override
public void onPageScrollStateChanged(int arg0) {}
@Override
public void onPageScrolled(int arg0, float arg1, int arg2) {}
}
The main XML file, my_activity.xml, containing the ViewPager, the TabHost and the Fragments for the ViewPager:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<android.support.v4.app.FragmentTabHost
android:id="@android:id/tabhost"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<FrameLayout
android:id="@android:id/tabcontent"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="0" />
<com.mycompany.myapp.gui.mypager.MyViewPager
android:id="@+id/my_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1" />
<TabWidget
android:id="@android:id/tabs"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</android.support.v4.app.FragmentTabHost>
<fragment
android:name="com.mycompany.myapp.gui.mypager.FilteredRecipesFragment"
android:id="@+id/filtered_recipes_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusable="true" />
<fragment
android:name="com.mycompany.myapp.gui.mypager.SelectedRecipesFragment"
android:id="@+id/selected_recipes_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusable="true" />
<fragment
android:name="com.mycompany.myapp.gui.mypager.ShoppingListFragment"
android:id="@+id/shopping_list_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusable="true" />
</LinearLayout>
Note that onCreateView
is called for each custom Fragment
, they are inflated and the root View
of each of them are returned. Here is one example, for FilteredRecipesFragment
. The other custom Fragments
are similar.
public class FilteredRecipesFragment extends Fragment {
private FilteredRecipesListFragment mFilteredRecipesListFragment;
private Button showRecipeFilterButton;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.filtered_recipes_fragment, container, false);
mFilteredRecipesListFragment = (FilteredRecipesListFragment)
getFragmentManager().findFragmentById(R.id.filtered_recipes_list_fragment);
showRecipeFilterButton = (Button) rootView.findViewById(R.id.show_recipe_filter_dialog_button);
showRecipeFilterButton.setOnClickListener(new RecipeFilterButtonListener());
return rootView;
}
}
Finally, the custom ViewPager
and its custom FragmentPagerAdapter
, where the program fails.
public class MyViewPager extends ViewPager{
private MyActivity mMyActivity;
private MyPagerAdapter mMyPagerAdapter;
public MyViewPager(Context context, AttributeSet attrs)
{
super(context, attrs);
mMyActivity = (MyActivity) context;
mMyPagerAdapter = new MyPagerAdapter(mMyActivity.getSupportFragmentManager(), mMyActivity);
this.setAdapter(mMyPagerAdapter);
this.setOnPageChangeListener(mMyActivity);
this.setCurrentItem(PagerConstants.PAGE_SHOPPING_LIST); // Page 0
}
}
MyPagerAdapter.java:
public class MyPagerAdapter extends FragmentPagerAdapter{
private MyActivity mMyActivity;
public MyPagerAdapter(FragmentManager fragmentManager, MyActivity myActivity)
{
super(fragmentManager);
mMyActivity = myActivity;
}
@Override
public Fragment getItem(int position)
{
switch (position) {
case PagerConstants.PAGE_FILTER_RECIPES: // 0
return mMyActivity.getSupportFragmentManager().findFragmentById(R.id.filtered_recipes_fragment);
case PagerConstants.PAGE_SELECTED_RECIPES: // 1
return mMyActivity.getSupportFragmentManager().findFragmentById(R.id.selected_recipes_fragment);
case PagerConstants.PAGE_SHOPPING_LIST: // 2
return mMyActivity.getSupportFragmentManager().findFragmentById(R.id.shopping_list_fragment);
default:
return null;
}
}
@Override
public int getCount()
{
return PagerConstants.NUMBER_OF_PAGES; // 3
}
@Override
public CharSequence getPageTitle(int position)
{
return PagerConstants.PAGE_TITLES(position);
}
}
Everything seems to be working ok, but after every custom Fragment
is inflated, the custom ViewPager
starts to add them too. Here is the stack output from Eclipse
:
06-07 15:37:49.815: E/AndroidRuntime(793): java.lang.IllegalStateException: Can't change container ID of fragment FilteredRecipesFragment{4605b0e0 #0 id=0x7f09003f android:switcher:2131296318:0}: was 2131296319 now 2131296318
BackStackRecord.doAddOp(int, Fragment, String, int) line: 407
BackStackRecord.add(int, Fragment, String) line: 389
MyPagerAdapter(FragmentPagerAdapter).instantiateItem(ViewGroup, int) line: 99
MyViewPager(ViewPager).addNewItem(int, int) line: 832
MyViewPager(ViewPager).populate(int) line: 982
MyViewPager(ViewPager).populate() line: 914
MyViewPager(ViewPager).onMeasure(int, int) line: 1436
MyViewPager(View).measure(int, int) line: 8171
LinearLayout(ViewGroup).measureChildWithMargins(View, int, int, int, int) line: 3132
... More calls <snipped>
In BackStackRecord.doAppOp
it fails because the container ID (i.e. the ID of the MyViewPager
is different from the Fragment
ID. Here is the code for that method:
private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {
fragment.mFragmentManager = mManager;
if (tag != null) {
if (fragment.mTag != null && !tag.equals(fragment.mTag)) {
throw new IllegalStateException("Can't change tag of fragment "
+ fragment + ": was " + fragment.mTag
+ " now " + tag);
}
fragment.mTag = tag;
}
if (containerViewId != 0) {
if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) {
// IT FAILS HERE!
throw new IllegalStateException("Can't change container ID of fragment "
+ fragment + ": was " + fragment.mFragmentId
+ " now " + containerViewId);
}
fragment.mContainerId = fragment.mFragmentId = containerViewId;
}
Op op = new Op();
op.cmd = opcmd;
op.fragment = fragment;
addOp(op);
}
I know that the container ID is the ID of the custom ViewPager
because it is its ID that is passed through in the instantiateItem(ViewGroup, int)
call. In my case, the ID of the MyViewPager
instance is 2131296319 and the ID of the Fragment
is 2131296318, hence it fails.
Where am I taking the wrong turn here? What am I misunderstanding in the whole ViewPager
/FragmentPagerAdapter
/Fragment
concept?
The problem is here:
you have to return a new instance of your
Fragment
s and not an existing one, which has already a parent and, hence, a container assinged. Remove theFragment
s you declared in your layout, and change yourgetItem
like