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:
#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;
}
you have to return a new instance of your Fragments and not an existing one, which has already a parent and, hence, a container assinged. Remove the Fragments you declared in your layout, and change your getItem like
#Override
public Fragment getItem(int position)
{
switch (position) {
case PagerConstants.PAGE_FILTER_RECIPES: // 0
return new FilteredRecipesFragment();
case PagerConstants.PAGE_SELECTED_RECIPES: // 1
return new SelectedRecipesFragment();
case PagerConstants.PAGE_SHOPPING_LIST: // 2
return new ShoppingListFragment()
default:
return null;
}
One of the possible reason for the issue is when you're trying to add the same fragment twice. Illustrated below
public void populateFragments() {
Fragment fragment = new Fragment();
//fragment is added for the first time
addFragment(fragment);
// fragment is added for the second time
// This call will be responsible for the issue. The
addFragment(fragment);
}
public void addFragment(Fragment fragment) {
FrameLayout frameLayout = AppView.createFrameLayout(context);
view.addView(frameLayout);
getSupportFragmentManager().beginTransaction().add(frameLayout.getId(), fragment).commit();
}
Related
Here when I landed to first fragment all views are loaded and scrolling but when I come from third fragment i.e from some other activity I need to land in third fragment,and when navigate to first fragment views are not loading which makes non scrollable.
Firstfragment xml contains recyclerview and Group elements.
MyActivity class:
MyAdapter adapter = new MyAdapter(this, getSupportFragmentManager(), fragments);
viewPager.setOffscreenPageLimit(3);
viewPager.setAdapter(adapter);
tabLayout.setupWithViewPager(viewPager);
//MyAdapter
public class MyAdapter extends FragmentPagerAdapter {
private Context mContext;
private List<Fragment> fragmentList ;
public MyAdapter(Context context, FragmentManager fm,List<Fragment> fragmentList) {
super(fm);
mContext = context;
this.fragmentList = fragmentList;
}
#Override
public Fragment getItem(int position) {
return fragmentList.get(position);
}
#Override
public int getCount() {
return 3;
}
#Override
public CharSequence getPageTitle(int position) {
switch (position) {
case 0:
return mContext.getString(R.string.firstfragmentname);
case 1:
return mContext.getString(R.string.seconsfrag);
case 2:
return mContext.getString(R.string.thirdfrag);
default:
return null;
}
}
}
//XML has custom viewpager
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="#dimen/headerHeight"
android:layout_marginBottom="#dimen/footerHeight"
android:fadingEdgeLength="0dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="#dimen/dp_30_60"
android:layout_marginTop="10dp"
android:layout_marginEnd="#dimen/dp_30_60"
android:orientation="vertical">
<TextView
android:id="#+id/sp_header"
style="#style/pageTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:text="#string/my_bill" />
<com.view.Tab
android:id="#+id/tab_custom"
android:layout_width="match_parent"
android:layout_height="#dimen/tab_height" />
<com.custom.CustomViewPager
android:id="#+id/sp_viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="#dimen/dp_10_53"
android:layout_marginTop="4dp"
android:layout_marginEnd="#dimen/dp_10_53"
android:layout_marginBottom="4dp" />
</LinearLayout>
</ScrollView>
//CustomViepager
public class CustomViepager extends ViewPager {
public CustomViepager(Context context) {
super(context);
initPageChangeListener();
}
public CustomViepager(Context context, AttributeSet attrs) {
super(context, attrs);
initPageChangeListener();
}
private void initPageChangeListener() {
addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
requestLayout();
}
});
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
View child = getChildAt(getCurrentItem());
if (child != null) {
child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
int h = child.getMeasuredHeight();
heightMeasureSpec = MeasureSpec.makeMeasureSpec(h, MeasureSpec.EXACTLY);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
#Override
public void setCurrentItem(int item) {
super.setCurrentItem(item, false);
}
#Override
public void setCurrentItem(int item, boolean smoothScroll) {
super.setCurrentItem(item, false);
}
}
Use a simple view pager and Try to use it like this
CustomPagerAdapter
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentStatePagerAdapter
class MyPagerAdapter(fm: FragmentManager, behaviour: Int) : FragmentStatePagerAdapter(fm, behaviour) {
private val mFragmentList = ArrayList<Fragment>()
override fun getItem(position: Int): Fragment {
return mFragmentList[position]
}
override fun getCount(): Int {
return mFragmentList.size
}
fun addFragment(fragment: Fragment) {
mFragmentList.add(fragment)
}
}
In Activity/Fragment
private var mAdapter: MyPagerAdapter? = null
in onCreate
mAdapter = MyPagerAdapter(getChildFragmentManager(), FragmentStatePagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT)
mAdapter?.addFragment(YOUR_FIRST_FRAGMENT)
mAdapter?.addFragment(YOUR_SECOND_FRAGMENT)
mAdapter?.addFragment(YOUR_THIRD_FRAGMENT)
sp_viewpager.offscreenPageLimit = 3
sp_viewPAger.adapter = mAdapter
When you want to land on the desired position, after the above code write
sp_viewpager.setCurrentItem(1/2/3,true/false)
#LakwinderSingh answer to this question is perfect.
Just want to highlight one thing is that, the main trick or real problem is with the offscreen page limit it's the number of fragments the pager creates left or right to the current fragment, by default this value is one.
That's why when we scroll to the third Fragment it doesn't load anything cause loading the third fragment will require loading 2 fragments at the same time to the left, by changing the offscreen page limit value we can determine the number of fragments that load up at a specific time in this case which would be 2.
If we did above then we also have to override PagerAdapter's default function destroyItem() to an empty function without super so that there aren't too many fragments destroyed and created at the same time cause that would make sliding choppy or make fragments blank.
so i have the classic viewpager in a tablayout that looks something like this:
My viewPagerAdapter class looks like this:
public class HomePagerAdapter extends FragmentPagerAdapter {
int mNumOfTabs;
public HomePagerAdapter(FragmentManager fm, int NumOfTabs) {
super(fm);
this.mNumOfTabs = NumOfTabs;
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
TabHomeFragment tab1 = new TabHomeFragment();
return tab1;
case 1:
TabShopFragment tab2 = new TabShopFragment();
return tab2;
case 2:
TabMeFragment tab3 = new TabMeFragment();
return tab3;
default:
return null;
}
}
#Override
public int getCount() {
return mNumOfTabs;
}
}
So now on one of the tabs i need to add a fragment on top of it. so its a search fragment really. i do a search in the tab bar and i want the back end search results to appear in only ONE of the tabs (the first one). but i want the search results displayed in a fragment called searchFragmentResults and i want it to be laid out on top of the first tab. Then when user hits the back button it will just go back to the original content in the first tab. Is this possible with view pager ?
so visually when i hit the search icon on the first tabs tabBar it should bring up a searchView and when i search for the users query it should bring up a another fragment with the results but only in the tab that it started from. Here is an example:
i cant call fragtransaction.replace(somecontentview, somefragment); (or its add method) because i did not add them to a contentview. i let the viewpager do it for me. So how is this achieved ?
i figured out how to do this. The tab should be a fragment who's purpose is to only contain other fragments. the idea is based off of this SO. But i had a need to do it for a viewPager. Lets go through the steps. first the viewpager adapter:
public class HomePagerAdapter extends FragmentStatePagerAdapter {
//integer to count number of tabs
int tabCount;
private Fragment mCurrentFragment;
private String[] tabTitles = new String[]{"tab0", "tab1", "tab2", "tab3"};
//Constructor to the class
public HomePagerAdapter(FragmentManager fm, int tabCount) {
super(fm);
this.tabCount = tabCount;
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
TabHomeContainerFragment tab1 = new Tab0ContainerFragment();
return tab1;
case 1:
TabShopFragment tab2 = new Tab1ContainerFragment();
return tab2;
case 2:
TabMeFragment tab3 = new Tab2ContainerFragment();
return tab3;
case 3:
TabBagFragment tab4 = new Tab3ContainerFragment();
return tab4;
default:
return null;
}
}
//Overriden method getCount to get the number of tabs
#Override
public int getCount() {
return tabCount;
}
public Fragment getCurrentFragment() {
return mCurrentFragment;
}
//* this is key to get the current tab to pop the fragments afterwards**/
#Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
if (getCurrentFragment() != object) {
mCurrentFragment = ((Fragment) object);
}
super.setPrimaryItem(container, position, object);
}
#Override
public CharSequence getPageTitle(int position) {
return tabTitles[position];
}
Lets go into a container to see how it would look:
add this to your xml for all the containers (along with anything else you want visually but they ALL must have the same id of container_framelayout:
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout android:id="#+id/container_framelayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android" />
</android.support.design.widget.CoordinatorLayout>
you dont have to put it in a coordinatorLayout i just find it fixes some bugs and works well.
In your fragments base class i copied almost the same code from the SO i mentioned above but slight modification if you want to add tag or not:
public void replaceFragment(Fragment fragment, boolean addToBackStack,String tag) {
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
if (addToBackStack) {
transaction.addToBackStack(null);
}
transaction.replace(R.id.container_framelayout, fragment,tag);
transaction.commit();
getChildFragmentManager().executePendingTransactions();
}
public boolean popFragment() {
Log.e("test", "pop fragment: " + getChildFragmentManager().getBackStackEntryCount());
boolean isPop = false;
if (getChildFragmentManager().getBackStackEntryCount() > 0) {
isPop = true;
getChildFragmentManager().popBackStack();
}
return isPop;
}
Now lets look at the activities layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v4.view.ViewPager
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="#color/white"/>
<android.support.design.widget.TabLayout
android:id="#+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabMode="fixed"
app:tabGravity="fill"
app:tabTextColor="#color/black"
app:tabSelectedTextColor="#android:color/darker_gray"
/>
Now i'll show you how to set up the tablayout in the activity hosting the tablayout:
its standard:
public class HomePageActivity implements TabLayout.OnTabSelectedListener {
private final int NUM_OF_TABS = 4;
#BindView(R.id.pager)
public ViewPager viewPager;
public HomePagerAdapter adapter;
#BindView(R.id.tabLayout)
TabLayout tabLayout;
#NonNull
#Override
public HomePagePresenter createPresenter() {
return new HomePagePresenter();
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_homepage);
ButterKnife.bind(this);
initView();
}
private void initView() {
for (int i = 0; i < NUM_OF_TABS; i++)
tabLayout.addTab(tabLayout.newTab());
adapter = new HomePagerAdapter(getSupportFragmentManager(), tabLayout.getTabCount());
//Adding adapter to pager
viewPager.setAdapter(adapter);
tabLayout.addOnTabSelectedListener(this);
tabLayout.setupWithViewPager(viewPager);
// configure tab icons
int[] imageTabResId = {
R.drawable.welcome1,
R.drawable.welcome2,
R.drawable.welcome3,
R.drawable.welcome1};
for (int i = 0; i < imageTabResId.length; i++) {
tabLayout.getTabAt(i).setIcon(imageTabResId[i]);
}
}
/** this is key. we get the current fragment showing and pop it **/
#Override
public void onBackPressed() {
boolean isPopFragment = false;
isPopFragment = ((BaseFragment) adapter.getCurrentFragment()).popFragment();
if (!isPopFragment) {
finish();
}
}
#Override
public void onTabSelected(TabLayout.Tab tab) {
viewPager.setCurrentItem(tab.getPosition());
}
#Override
public void onTabUnselected(TabLayout.Tab tab) {
}
#Override
public void onTabReselected(TabLayout.Tab tab) {
}
}
I guess the major part is onBackPress getting the current fragment.
I'm having navigation drawer with four menu's and each menu has own fragments
, Inside first fragment has view pager sliding tab with 2 fragments,
Activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/drawer_layout"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:fitsSystemWindows="true">
<android.support.design.widget.CoordinatorLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light" />
<android.support.design.widget.TabLayout
android:id="#+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabIndicatorColor="#color/white"
app:tabIndicatorHeight="4dp" />
</android.support.design.widget.AppBarLayout>
////////////// Here I'm replacing each fragments ////////////////
<FrameLayout
android:id="#+id/main_content_framelayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior" />
<android.support.design.widget.FloatingActionButton
android:id="#+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_margin="#dimen/fab_margin"
android:src="#drawable/ic_done"
android:visibility="gone"/>
</android.support.design.widget.CoordinatorLayout>
<android.support.design.widget.NavigationView
android:id="#+id/nav_view"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="#layout/nav_header"
app:menu="#menu/drawer_view"/>
</android.support.v4.widget.DrawerLayout>
ManinActivity- DrawerSelection
public void selectDrawerItem(MenuItem menuItem) {
// Create a new fragment and specify the fragment to show based on nav item clicked
Fragment fragment = null;
Class fragmentClass = null;
switch (menuItem.getItemId()) {
case R.id.nav_home:
fragmentClass = Home_Tab_Fragment.class;
break;
case R.id.nav_myorders:
fragmentClass = SecondFragment.class;
break;
case R.id.nav_myoffers:
fragmentClass = ThirdFragment.class;
break;
case R.id.nav_notification:
fragmentClass = FourthFragment.class;
break;
}
fragment = (Fragment) fragmentClass.newInstance();
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction().replace(R.id.main_content_framelayout, fragment).commit();
mDrawerLayout.closeDrawers();
}
HomeTabFragment
public class Home_Tab_Fragment extends Fragment {
private FragmentActivity myContext;
ViewPager viewPager;
TabLayout tabLayout;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.layout_viewpager, container, false);
viewPager = (ViewPager) rootView.findViewById(R.id.view_pager);
return rootView;
}
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
SlidingTabAdapter adapter = new SlidingTabAdapter(getChildFragmentManager());
adapter.addFragment(new FirstTabFragment(), "First TAB");
adapter.addFragment(new SecondTabFragment(), "Second Tab");
viewPager.setAdapter(adapter);
tabLayout = (TabLayout) getActivity().findViewById(R.id.tabs);
tabLayout.setVisibility(View.VISIBLE);
tabLayout.setupWithViewPager(viewPager);
}
}
SlidingTabAdapter
public class SlidingTabAdapter extends FragmentPagerAdapter {
private final List<Fragment> mFragments = new ArrayList<>();
private final List<String> mFragmentTitles = new ArrayList<>();
public SlidingTabAdapter(FragmentManager fm) {
super(fm);
}
public void addFragment(Fragment fragment, String title) {
mFragments.add(fragment);
mFragmentTitles.add(title);
}
#Override
public Fragment getItem(int position) {
return mFragments.get(position);
}
#Override
public int getCount() {
return mFragments.size();
}
#Override
public CharSequence getPageTitle(int position) {
return mFragmentTitles.get(position);
}
}
layout_viewpager.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.view.ViewPager
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
FirstFragment
public class FirstTabFragment extends Fragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_first, container, false);
getActivity().setTitle("Foodies");
return rootView ;
}
public void updateFirstFragmentValues(){
//Doing some operations
}
}
Inside MainActivity , I want to call FirstTabFragment method [updateFirstFragmentValues()]
I tried adding TAG in FirstTabFragment ,
public static final String TAG ="FirstTabFragment.TAG";
Then I invoked in MainActivity like below , but Fragment always null.
public void invokeMethodFromFirstTabFragment() {
FragmentManager fm = this.getSupportFragmentManager();
FirstTabFragment fb=(FirstTabFragment)fm.findFragmentByTag(FirstTabFragment.TAG);
if (null != fb) {
fb.updateFirstFragmentValues();
}else{
L.m("Fragment is null===========");
}
}
Kindly advise , How to call "FirstTabFragment" method from inside "MainActivity"
Please note FirstTabFragment is not added directly to MainActivity ,
its added through "Home_Tab_Fragment".
Alright, reading all of the responses of yours, I get that you just simply need to pass data to a fragment upon creation and be able to execute some of Fragment's methods. Ok, simple enough:
IMessageFragment interface
public interface IMessageFragment {
/**
* Method to receive new message text
* #param text Message text to receive
*/
void updateMessageText(String text);
}
MessageFragment
public class MessageFragment extends Fragment implements IMessageFragment {
/**
* Bind views using ButterKnife
*/
#BindView(R.id.textView) TextView mTextView;
/**
* Bundle data
*/
private String mMessageText;
/**
* Unique id of bundle data
*/
private static final String MESSAGE_EXTRA_KEY = "f_message";
/**
* New fragment pattern. This is pretty much all the magic PLUS this
* looks by far better than `new Fragment()` upon instatiation
*/
public static MessageFragment newFragment(String message) {
// 1. Create new fragment
MessageFragment auctionFragment = new MessageFragment();
// 2. Create bundle for fragment params
Bundle args = new Bundle();
// 3. Put position
args.putString(MESSAGE_EXTRA_KEY, message);
// 4. Set arguments
auctionFragment.setArguments(args);
return auctionFragment;
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.tutorial_fragment, container, false);
/**
* Bind views
*/
ButterKnife.bind(this, view);
// Update UI
updateUI();
return view;
}
/**
* Method to update value of mTextView
*/
private void updateUI() {
if(!mMessageText.equals("")) {
mTextView.setText(mMessageText);
}
}
#Override
public void updateMessageText(String text) {
mMessageText = text;
updateUI();
}
}
Stuff is pretty simple and the interface exists only for good code style and to support design patterns.
Edit:
You cannot directly access FirstTabFragment method from your MainActivity. Firstly you have to call Home_Tab_Fragment method from activity. Then from that method you can call FirstTabFragment method.
Here is strategy:
Step 1: Home_Tab_Fragment to FirstTabFragment communication
define an interface like this
public interface IHomeToFirstFragmentController{
void firstFragmentMethod();
}
Now implements the IHomeToFirstFragmentController in your FirstTabFragment. then you have to implements the firstFragmentMethod().
#overrride
void firstFragmentMethod(){
}
Call this firstFragmentMethod method from Home_Tab_Fragment using this way
YOUR_fragmentInstance.firstFragmentMethod();
Step 2: Activity to Home_Tab_Fragment:
define an interface like this
public interface IActivityToHomeTabController{
void callFirstFragment();
}
Now implements the IFragmentController in your Home_Tab_Fragment. then you have to implements the callFirstFragment().
#overrride
void callFirstFragment(){
YOUR_fragmentInstance.firstFragmentMethod();
}
Edit 2:
Call fragment using this way
((IFragmentController)YOUR_fragmentInstance).firstFragmentMethod();
Hope this will give you an idea about this topics. For more info visit this answer
First for all, you create interface :
public interface CallBackListener{
void onCallBack();
}
In Fragment
public MyFragment extends Fragment{
private CallBackListener mCallBack;
#Override
public void onAttach(Context context) {
AppCompatActivity activity;
if (context instanceof AppCompatActivity) {
activity = (AppCompatActivity) context;
try {
mCallBack= (CallBackListener ) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " class must be implement" +
" OnBackFragmentListener");
}
}
super.onAttach(context);
}
}
Use :
mCallBack.onCallBack();
And in Activity, you must implements interface :
public MyActivity extends AppCompatActivity implements CallBackListener{
#Override
public void onCallBack(){
// Do somethings here...
}
}
I'm new to Fragments. I'm trying to develop swipe tabs.
I have the following Adapter:
public class HomeTabsPagerAdapter extends FragmentPagerAdapter {
public HomeTabsPagerAdapter(android.support.v4.app.FragmentManager fm) {
super(fm);
}
#Override
public android.support.v4.app.Fragment getItem(int idx) {
switch (idx) {
case CONSTS_MAIN.TAB_WATCH:
return new WatchFragment();
case CONSTS_MAIN.TAB_DISCOVER:
return new DiscoverFragment();
case CONSTS_MAIN.TAB_LOG:
return new LogFragment();
}
return null;
}
#Override
public int getCount() {
return CONSTS_MAIN.HOME_NO_OF_TABS;
}
}
and MainActivity.java
public class MainActivity extends FragmentActivity implements ActionBar.TabListener {
}
and of course, the Fragment classes:
public class WatchFragment extends Fragment {
public class DiscoverFragment extends Fragment {
public class LogFragment extends Fragment {
And basically everything seems to work fine graphically.
However, I'm trying to call method of the Fragment from the Activity. I'm doing it this way:
FragmentManager fm = getSupportFragmentManager();
WatchFragment watchFragment = (WatchFragment) fm.findFragmentById(R.id.watch_fragment);
watchFragment.refresh();
and here fm.findFragmentById always return null.
I'm using android.support.v4.app all the way.
How can I get the Fragment class?
EDIT:
Here is the Fragment's layout:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id ="#+id/watch_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:label="#string/title_activity_watch"
android:orientation="vertical" >
<com.x.y.custom_controls.CustomWatchChronologicalListView
android:id="#+id/lvWatchChronologicalDiscussions"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
and here is the Activity's layout
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.view.ViewPager
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</android.support.v4.view.ViewPager>
</RelativeLayout>
Use this:
In your adapter add a SparseArray to keep references to the fragments added
private final SparseArray<Fragment> mPageReferences = new SparseArray<Fragment>();
In your getItem method after instantiate the fragment add it to the array:
#Override
public android.support.v4.app.Fragment getItem(int idx)
{
Fragment fragment = null;
switch (idx)
{
case CONSTS_MAIN.TAB_WATCH:
fragment = new WatchFragment();
case CONSTS_MAIN.TAB_DISCOVER:
fragment = new DiscoverFragment();
case CONSTS_MAIN.TAB_LOG:
fragment = new LogFragment();
}
if(fragment != null) {
mPageReferences.put( position, fragment );
}
return fragment;
}
And add the followings methods:
#Override
public void destroyItem(ViewGroup container, int position, Object object)
{
super.destroyItem(container, position, object);
mPageReferences.remove(position);
}
public SherlockFragment getFragment(int key) {
return mPageReferences.get(key);
}
Finally you can get the fragment in the index you want like this:
Fragment fragment = (Fragment)adapter.getFragment(index);
If you want to use keys to identify the fragments instead int index, you can use a HashMap
Good Luck!
You cannot do findFragmentById if your not defining the fragment using the fragment tag in xml.
Here you are passing id of a relative layout to the method findFragmentById.
The approach is to use the fragment instance you get from the fragmentMnager-
WatchFragment watchFragment=(WatchFragment)getFragment(0);
watchFragment.refresh();
//getFragment Method
FragmentManager fragmentManager = getSupportFragmentManager();
List<Fragment> fragmentList=fragmentManager.getFragments();
public Fragment getFragment(int index){
for (Fragment fragment:fragmentList){
if (fragment.getId()==index)
return fragment;
}
}
There are some questions related to this same issue. For example, this one. But it doesn't work.
Let me show what I did in my code.
activity_main.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:id="#+id/container"
android:layout_width="match_parent" android:layout_height="match_parent"
tools:context=".MainActivity" tools:ignore="MergeRootFrame" >
<fragment
android:id="#+id/my_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="com.example.zhouhao.test.MyFragment"
tools:layout="#layout/fragment_my" />
</FrameLayout>
fragment_my.xml (my main fragment which include a FragmentTabHost)
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.app.FragmentTabHost xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#android:id/tabhost"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:weightSum="100"
android:orientation="horizontal">
<FrameLayout
android:id="#android:id/tabcontent"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="0" />
<LinearLayout
android:layout_width="0dp"
android:layout_weight="95"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:id="#+id/panel_content"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
</LinearLayout>
</android.support.v4.app.FragmentTabHost>
fragment_tab1.xml (for the fragment corresponding to different tab, they are similar so that I only show you one code)
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" tools:context="com.example.zhouhao.test.TabFragment">
<!-- TODO: Update blank fragment layout -->
<TextView android:layout_width="match_parent" android:layout_height="match_parent"
android:text="#string/fragment_tab1" />
</FrameLayout>
MainActivity.java
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
FragmentManager fm = getSupportFragmentManager();
mMyFragment = (MyFragment) fm.findFragmentById(R.id.my_fragment);
}
}
public class MyFragment extends Fragment implements TabHost.OnTabChangeListener {
FragmentTabHost mFragmentTabHost;
public MyFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_my, container, false);
if (rootView != null) {
mFragmentTabHost = (FragmentTabHost) rootView.findViewById(android.R.id.tabhost);
mFragmentTabHost.setup(getActivity(), getActivity().getSupportFragmentManager(), R.id.panel_content);
TabFragment1 tab1Fragment = new TabFragment1();
TabFragment2 tab2Fragment = new TabFragment2();
TabFragment3 tab3Fragment = new TabFragment3();
TabHost.TabSpec spec1 = mFragmentTabHost.newTabSpec("1").setIndicator("");
TabHost.TabSpec spec2 = mFragmentTabHost.newTabSpec("2").setIndicator("");
TabHost.TabSpec spec3 = mFragmentTabHost.newTabSpec("3").setIndicator("");
mFragmentTabHost.addTab(spec1,tab1Fragment.getClass(), null);
mFragmentTabHost.addTab(spec2,tab2Fragment.getClass(), null);
mFragmentTabHost.addTab(spec3,tab3Fragment.getClass(), null);
mFragmentTabHost.setOnTabChangedListener(this);
return rootView;
} else {
return super.onCreateView(inflater, container, savedInstanceState);
}
}
#Override
public void onTabChanged(String tabId) {
BaseFragment f = (BaseFragment)getActivity().getSupportFragmentManager().findFragmentByTag(tabId);
Log.d("Log",tabId);
}
}
My problem is the BaseFragment is always null in my onTabChanged. Can anybody help? Thanks.
You cannot get the selected fragment immediately if the fragment has never been instantiated.
#Override
public void onTabChanged(final String tabId) {
Fragment fg = getSupportFragmentManager().findFragmentByTag(tabId);
Log.d(TAG, "onTabChanged(): " + tabId + ", fragment " + fg);
if (fg == null) {
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
Fragment fg = getSupportFragmentManager().findFragmentByTag(tabId);
Log.d(TAG, "onTabChanged() delay 50ms: " + tabId + ", fragment " + fg);
}
}, 50);
}
}
the output:
// cannot get the selected fragment immediately if the fragment has never been instantiated.
onTabChanged(): 1, fragment null
onTabChanged() delay 50ms: 1, fragment HistoryFragment{6f7a9d5 #2 id=0x7f09006e 1}
onTabChanged(): 2, fragment null
onTabChanged() delay 50ms: 2, fragment HistoryFragment{10c59e72 #3 id=0x7f09006e 2}
// can get the selected fragment immediately if the fragment already instantiated.
onTabChanged(): 1, fragment HistoryFragment{6f7a9d5 #2 id=0x7f09006e 1}
onTabChanged(): 2, fragment HistoryFragment{10c59e72 #3 id=0x7f09006e 2}
I think I get the right way to solve this problem, below is my answer.
Create a TabBean
private int imgId;
private int textId;
private Class fragment;
public MainTabBean(int textId, int imgId, Class fragment) {
this.textId = textId;
this.imgId = imgId;
this.fragment = fragment;
}
public int getTextId() {
return textId;
}
public void setTextId(int textId) {
this.textId = textId;
}
public int getImgId() {
return imgId;
}
public void setImgId(int imgId) {
this.imgId = imgId;
}
public Class getFragment() {
return fragment;
}
public void setFragment(Class fragment) {
this.fragment = fragment;
}
Then, get three TabBean instances with three fragments
List<MainTabBean> tabs = new ArrayList<>();
// three TabBean instances
TabBean homeTab = new MainTabBean(R.string.home, R.drawable.tab_home, HomepageFragment.class);
TabBean postcardTab = new MainTabBean(R.string.postcard, R.drawable.tab_postcard, PostcardFragment.class);
Bean notificationTab = new MainTabBean(R.string.notification, R.drawable.tab_notification, NotificationFragment.class);
// put tabs into a list
tabs.add(homeTab);
tabs.add(postcardTab);
tabs.add(notificationTab);
Third, just associate the TabBeans with FragmentTabHost as usual
// Obtain FragmentTabHost
tabHost = findViewById(android.R.id.tabhost);
// Associate fragmentManager with container
tabHost.setup(this, getSupportFragmentManager(), R.id.main_content);
// Create TabSpecs with TabBean and add it on TabHost
for(MainTabBean tab : tabs) {
TabHost.TabSpec spec = tabHost.newTabSpec(String.valueOf(tab.getTextId()))// set a tag
.setIndicator(getTabIndicator(tab));// set an indicator
tabHost.addTab(spec, tab.getFragment(), null);// add a tab
}
Forth, that's the most important step, I didn't get fragment instance with findFragmentByTag() like this
getSupportFragmentManager()
.findFragmentByTag(String.valueOf(tabs.get(1).getTextId()));
Instead, my answer is exactly like this:
tabs.get(0).getFragment().newInstance();
and cast it.
And I finally got the fragment.