Tabs + ViewPager + FragmentStatePagerAdapter - How to remove fragment? - android

I'm using the "Tabs + Swipe" project and I'm having a difficult time removing a fragment.
Steps I'm doing:
Remove tab from database
Remove tab from the FragmentStatePagerAdapter data source
Remove tab from the actionBar.
Remove fragment using Support FragmentManager.
The problem:
After I perform the remove, for some reason, I can still scroll to the right and see an empty fragment. I can't select it, it just bounces back.
It seems like the fragment is not being removed but rather changes its position to the tag from the left.
My Adapter:
public class SectionsPagerAdapter extends FragmentStatePagerAdapter {
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
Fragment fragment = new TabFragment();
Bundle args = new Bundle();
args.putInt(TabFragment.ARG_TAB_POSITION, position);
fragment.setArguments(args);
return fragment;
}
#Override
public int getCount() {
return tabsList.size();
}
#Override
public CharSequence getPageTitle(int position) {
return tabsList.get(position).getTitle();
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
super.destroyItem(container, position, object);
FragmentManager manager = ((Fragment)object).getFragmentManager();
android.support.v4.app.FragmentTransaction trans = manager.beginTransaction();
trans.remove((Fragment)object);
trans.commit();
}
}
My remove method (called from within the fragment):
public void removeTab() {
mTabTableHandler.deleteTab(tab.getId()); //db
tabsList.remove(tabPosition); //data source
actionBar.removeTabAt(tabPosition); // actionbar
getActivity().getSupportFragmentManager().beginTransaction().remove(this).commit(); // support fragmentmanager
tabsList = mTabTableHandler.query(); //requery db
mSectionsPagerAdapter.notifyDataSetChanged(); //notify adapter
}
Appreciate the help!

you can find the answer here -
Remove Fragment Page from ViewPager in Android
it seems what missing in your code is overwriting of the following adapter's method:
#Override
public int getItemPosition(Object object){
return PagerAdapter.POSITION_NONE;
}
explanation why such "weird" solution works you can find in the answer to the question in the link I provided you.

public void removeAllFragments(ViewGroup container) {
/*
* keep this line because section pager adapter has
* the bug to not delete the last fragment completely
* -> so we add an empty fragment
*/
int iStart = 0;
addFragment(new Fragment());
Fragment frag;
int size = fragments.size();
for (int iPos = iStart; iPos < size; iPos++) {
frag = getItem(iStart);
if (frag.isAdded()) {
destroyItem(container, iStart, frag);
}
fragments.remove(frag);
notifyDataSetChanged();
}
}

Related

How can I get the current Fragment from ViewPager? [duplicate]

Below is my code which has 3 Fragment classes each embedded with each of the 3 tabs on ViewPager. I have a menu option. As shown in the onOptionsItemSelected(), by selecting an option, I need to update the fragment that is currently visible. To update that I have to call a method which is in the fragment class. Can someone please suggest how to call that method?
public class MainActivity extends ActionBarActivity {
ViewPager ViewPager;
TabsAdapter TabsAdapter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ViewPager = new ViewPager(this);
ViewPager.setId(R.id.pager);
setContentView(ViewPager);
final ActionBar bar = getSupportActionBar();
bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
//Attaching the Tabs to the fragment classes and setting the tab title.
TabsAdapter = new TabsAdapter(this, ViewPager);
TabsAdapter.addTab(bar.newTab().setText("FragmentClass1"),
FragmentClass1.class, null);
TabsAdapter.addTab(bar.newTab().setText("FragmentClass2"),
FragmentClass2.class, null);
TabsAdapter.addTab(bar.newTab().setText("FragmentClass3"),
FragmentClass3.class, null);
if (savedInstanceState != null) {
bar.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0));
}
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.addText:
**// Here I need to call the method which exists in the currently visible Fragment class**
return true;
}
return super.onOptionsItemSelected(item);
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("tab", getSupportActionBar().getSelectedNavigationIndex());
}
public static class TabsAdapter extends FragmentPagerAdapter
implements ActionBar.TabListener, ViewPager.OnPageChangeListener {
private final Context mContext;
private final ActionBar mActionBar;
private final ViewPager mViewPager;
private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>();
static final class TabInfo {
private final Class<?> clss;
private final Bundle args;
TabInfo(Class<?> _class, Bundle _args) {
clss = _class;
args = _args;
}
}
public TabsAdapter(ActionBarActivity activity, ViewPager pager) {
super(activity.getSupportFragmentManager());
mContext = activity;
mActionBar = activity.getSupportActionBar();
mViewPager = pager;
mViewPager.setAdapter(this);
mViewPager.setOnPageChangeListener(this);
}
public void addTab(ActionBar.Tab tab, Class<?> clss, Bundle args) {
TabInfo info = new TabInfo(clss, args);
tab.setTag(info);
tab.setTabListener(this);
mTabs.add(info);
mActionBar.addTab(tab);
notifyDataSetChanged();
}
#Override
public void onPageScrollStateChanged(int state) {
// TODO Auto-generated method stub
}
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
// TODO Auto-generated method stub
}
#Override
public void onPageSelected(int position) {
// TODO Auto-generated method stub
mActionBar.setSelectedNavigationItem(position);
}
#Override
public void onTabReselected(Tab tab, FragmentTransaction ft) {
// TODO Auto-generated method stub
}
#Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
Object tag = tab.getTag();
for (int i=0; i<mTabs.size(); i++) {
if (mTabs.get(i) == tag) {
mViewPager.setCurrentItem(i);
}
}
tabPosition = tab.getPosition();
}
#Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
// TODO Auto-generated method stub
}
#Override
public Fragment getItem(int position) {
TabInfo info = mTabs.get(position);
return Fragment.instantiate(mContext, info.clss.getName(), info.args);
}
#Override
public int getCount() {
return mTabs.size();
}
}
}
Suppose below is the fragment class with the method updateList() I want to call:
public class FragmentClass1{
ArrayList<String> originalData;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View fragmentView = inflater.inflate(R.layout.frag1, container, false);
originalData = getOriginalDataFromDB();
return fragmentView;
}
public void updateList(String text)
{
originalData.add(text);
//Here I could do other UI part that need to added
}
}
by selecting an option, I need to update the fragment that is
currently visible.
A simple way of doing this is using a trick related to the FragmentPagerAdapter implementation:
case R.id.addText:
Fragment page = getSupportFragmentManager().findFragmentByTag("android:switcher:" + R.id.pager + ":" + ViewPager.getCurrentItem());
// based on the current position you can then cast the page to the correct
// class and call the method:
if (ViewPager.getCurrentItem() == 0 && page != null) {
((FragmentClass1)page).updateList("new item");
}
return true;
Please rethink your variable naming convention, using as the variable name the name of the class is very confusing(so no ViewPager ViewPager, use ViewPager mPager for example).
public class MyPagerAdapter extends FragmentPagerAdapter {
private Fragment mCurrentFragment;
public Fragment getCurrentFragment() {
return mCurrentFragment;
}
//...
#Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
if (getCurrentFragment() != object) {
mCurrentFragment = ((Fragment) object);
}
super.setPrimaryItem(container, position, object);
}
}
First of all keep track of all the "active" fragment pages. In this case, you keep track of the fragment pages in the FragmentStatePagerAdapter, which is used by the ViewPager.
#Override
public Fragment getItem(int index) {
Fragment myFragment = MyFragment.newInstance();
mPageReferenceMap.put(index, myFragment);
return myFragment;
}
To avoid keeping a reference to "inactive" fragment pages, you need to implement the FragmentStatePagerAdapter's destroyItem(...) method:
#Override
public void destroyItem (ViewGroup container, int position, Object object) {
super.destroyItem(container, position, object);
mPageReferenceMap.remove(position);
}
and when you need to access the currently visible page, you then call:
int index = mViewPager.getCurrentItem();
MyAdapter adapter = ((MyAdapter)mViewPager.getAdapter());
MyFragment fragment = adapter.getFragment(index);
Where the MyAdapter's getFragment(int) method looks like this:
public MyFragment getFragment(int key) {
return mPageReferenceMap.get(key);
}
Hope it may help!
This is the only way I don't get NullPointerException for the instance variables of that particular fragment classes. This might be helpful for others who stuck at the same thing. In the onOptionsItemSelected(), I coded the below way:
if(viewPager.getCurrentItem() == 0) {
FragmentClass1 frag1 = (FragmentClass1)viewPager
.getAdapter()
.instantiateItem(viewPager, viewPager.getCurrentItem());
frag1.updateList(text);
} else if(viewPager.getCurrentItem() == 1) {
FragmentClass2 frag2 = (FragRecentApps)viewPager
.getAdapter()
.instantiateItem(viewPager, viewPager.getCurrentItem());
frag2.updateList(text);
}
FragmentStatePagerAdapter has public method with the name instantiateItem that return your fragment based on specified parameter values, this method has two parameters ViewGroup (ViewPager) and position.
public Object instantiateItem(ViewGroup container, int position);
Used this method to get specified position's fragment,
Fragment fragment = (Fragment) adaper.instantiateItem(mViewPager, position);
I know its too late but I have really simple ways of doing it,
// for fragment at 0 possition
((mFragment) viewPager.getAdapter().instantiateItem(viewPager, 0)).yourMethod();
getSupportFragmentManager().getFragments().get(viewPager.getCurrentItem());
Cast the instance retreived from above line to the fragment you want to work on with. Works perfectly fine.
viewPager
is the pager instance managing the fragments.
There are a lot of answers here that don't really address the basic fact that there's really NO WAY to do this predictably, and in a way that doesn't result you shooting yourself in the foot at some point in the future.
FragmentStatePagerAdapter is the only class that knows how to reliably access the fragments that are tracked by the FragmentManager - any attempt to try and guess the fragment's id or tag is not reliable, long-term. And attempts to track the instances manually will likely not work well when state is saved/restored, because FragmentStatePagerAdapter may well not call the callbacks when it restores the state.
About the only thing that I've been able to make work is copying the code for FragmentStatePagerAdapter and adding a method that returns the fragment, given a position (mFragments.get(pos)). Note that this method assumes that the fragment is actually available (i.e. it was visible at some point).
If you're particularly adventurous, you can use reflection to access the elements of the private mFragments list, but then we're back to square one (the name of the list is not guaranteed to stay the same).
by selecting an option, I need to update the fragment that is currently visible.
To get a reference to currently visible fragment, assume you have a reference to ViewPager as mPager. Then following steps will get a reference to currentFragment:
PageAdapter adapter = mPager.getAdapter();
int fragmentIndex = mPager.getCurrentItem();
FragmentStatePagerAdapter fspa = (FragmentStatePagerAdapter)adapter;
Fragment currentFragment = fspa.getItem(fragmentIndex);
The only cast used line 3 is valid usually. FragmentStatePagerAdapter is an useful adapter for a ViewPager.
Best way to do this, just call CallingFragmentName fragment = (CallingFragmentName) viewPager .getAdapter() .instantiateItem(viewPager, viewPager.getCurrentItem()); It will re-instantiate your calling Fragment, so that it will not throw null pointer exception and call any method of that fragment.
Current Fragment:
This works if you created a project with the fragments tabbar template.
Fragment f = mSectionsPagerAdapter.getItem(mViewPager.getCurrentItem());
Note that this works with the default tabbed activity template implementation.
I have used the following:
int index = vpPager.getCurrentItem();
MyPagerAdapter adapter = ((MyPagerAdapter)vpPager.getAdapter());
MyFragment suraVersesFragment = (MyFragment)adapter.getRegisteredFragment(index);
When we use the viewPager, a good way to access the fragment instance in activity is instantiateItem(viewpager,index). //index- index of fragment of which you want instance.
for example I am accessing the fragment instance of 1 index-
Fragment fragment = (Fragment) viewPageradapter.instantiateItem(viewPager, 1);
if (fragment != null && fragment instanceof MyFragment) {
((MyFragment) fragment).callYourFunction();
}
In my previous implementation I stored a list of child Fragments to be able to access them later, but this turned out to be a wrong implementation causing huge memory leaks.
I end up using instantiateItem(...) method to get current Fragment:
val currentFragment = adapter?.instantiateItem(viewPager, viewPager.currentItem)
Or to get any other Fragment on position:
val position = 0
val myFirstFragment: MyFragment? = (adapter?.instantiateItem(viewPager, position) as? MyFragment)
From documentation:
Create the page for the given position. The adapter is responsible for
adding the view to the container given here, although it only must
ensure this is done by the time it returns from
finishUpdate(ViewGroup).
FragmentStatePagerAdapter has a private instance variable called mCurrentPrimaryItem of type Fragment. One can only wonder why Android devs did not supplied it with a getter. This variable is instantiated in setPrimaryItem() method. So, override this method in such a way for you to get the reference to this variable. I simply ended up with declaring my own mCurrentPrimaryItem and copying the contents of setPrimaryItem() to my override.
In your implementation of FragmentStatePagerAdapter:
private Fragment mCurrentPrimaryItem = null;
#Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment)object;
if (fragment != mCurrentPrimaryItem) {
if (mCurrentPrimaryItem != null) {
mCurrentPrimaryItem.setMenuVisibility(false);
mCurrentPrimaryItem.setUserVisibleHint(false);
}
if (fragment != null) {
fragment.setMenuVisibility(true);
fragment.setUserVisibleHint(true);
}
mCurrentPrimaryItem = fragment;
}
}
public TasksListFragment getCurrentFragment() {
return (YourFragment) mCurrentPrimaryItem;
}
You can define the PagerAdapter like this then you will able to get any Fragment in ViewPager.
private class PagerAdapter extends FragmentPagerAdapter {
private final List<Fragment> mFragmentList = new ArrayList<>();
public PagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
return mFragmentList.get(position);
}
#Override
public int getCount() {
return mFragmentList.size();
}
public void addFragment(Fragment fragment) {
mFragmentList.add(fragment);
}
}
To get the current Fragment
Fragment currentFragment = mPagerAdapter.getItem(mViewPager.getCurrentItem());
After reading all comments and answers I am going to explain an optimal solution for this problem. The best option is #rik's solution, so my improvement is based on his.
Instead of having to ask each FragmentClass like
if(FragmentClass1){
...
if(FragmentClass2){
...
}
Create your own interface, and make your child fragments implement it, something like
public interface MyChildFragment {
void updateView(int position);
}
Then, you can initiate and update your inner fragments with
Fragment childFragment = (Fragment) mViewPagerDetailsAdapter.instantiateItem(mViewPager,mViewPager.getCurrentItem());
if (childFragment != null) {
((MyChildFragment) childFragment).updateView();
}
P.S. Be careful where you put that code, if you call insatiateItem before the system actually creates it the savedInstanceState of your child fragment will be null therefor
public void onCreate(#Nullable Bundle savedInstanceState){
super(savedInstanceState)
}
Will crash your app.
Good luck
I had the same issue and solved it using this code.
MyFragment fragment = (MyFragment) thisActivity.getFragmentManager().findFragmentById(R.id.container);
Just replace the name MyFragment with the name of your fragment and add the id of your fragment container.
This is more future-proof than the accepted answer:
public class MyFragmentPagerAdapter extends FragmentPagerAdapter {
/* ------------------------------------------------------------------------------------------ */
// region Private attributes :
private Context _context;
private FragmentManager _fragmentManager;
private Map<Integer, String> _fragmentsTags = new HashMap<>();
// endregion
/* ------------------------------------------------------------------------------------------ */
/* ------------------------------------------------------------------------------------------ */
// region Constructor :
public MyFragmentPagerAdapter(Context context, FragmentManager fragmentManager) {
super(fragmentManager);
_context = context;
_fragmentManager = fragmentManager;
}
// endregion
/* ------------------------------------------------------------------------------------------ */
/* ------------------------------------------------------------------------------------------ */
// region FragmentPagerAdapter methods :
#Override
public int getCount() { return 2; }
#Override
public Fragment getItem(int position) {
if(_fragmentsTags.containsKey(position)) {
return _fragmentManager.findFragmentByTag(_fragmentsTags.get(position));
}
else {
switch (position) {
case 0 : { return Fragment.instantiate(_context, Tab1Fragment.class.getName()); }
case 1 : { return Fragment.instantiate(_context, Tab2Fragment.class.getName()); }
}
}
return null;
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
// Instantiate the fragment and get its tag :
Fragment result = (Fragment) super.instantiateItem(container, position);
_fragmentsTags.put(position, result.getTag());
return result;
}
// endregion
/* ------------------------------------------------------------------------------------------ */
}
The scenario in question is better served by each Fragment adding its own menu items and directly handling onOptionsItemSelected(), as described in official documentation. It is better to avoid undocumented tricks.
If your pager is inside a Fragment then use this:
private fun getPagerCurrentFragment(): Fragment? {
return childFragmentManager.findFragmentByTag("android:switcher:${R.id.myViewPagerId}:${myViewPager.currentItem}")
}
Where R.id.myViewPagerId is the id of your ViewPager inside the xml Layout.
Based on what he answered #chahat jain :
"When we use the viewPager, a good way to access the fragment instance in activity is instantiateItem(viewpager,index). //index- index of fragment of which you want instance."
If you want to do that in kotlin
val fragment = mv_viewpager.adapter!!.instantiateItem(mv_viewpager, 0) as Fragment
if ( fragment is YourFragmentFragment)
{
//DO somthign
}
0 to the fragment instance of 0
//=========================================================================//
//#############################Example of uses #################################//
//=========================================================================//
Here is a complete example to get a losest vision about
here is my veiewPager in the .xml file
...
<android.support.v4.view.ViewPager
android:id="#+id/mv_viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="5dp"/>
...
And the home activity where i insert the tab
...
import kotlinx.android.synthetic.main.movie_tab.*
class HomeActivity : AppCompatActivity() {
lateinit var adapter:HomeTabPagerAdapter
override fun onCreate(savedInstanceState: Bundle?) {
...
}
override fun onCreateOptionsMenu(menu: Menu) :Boolean{
...
mSearchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
...
override fun onQueryTextChange(newText: String): Boolean {
if (mv_viewpager.currentItem ==0)
{
val fragment = mv_viewpager.adapter!!.instantiateItem(mv_viewpager, 0) as Fragment
if ( fragment is ListMoviesFragment)
fragment.onQueryTextChange(newText)
}
else
{
val fragment = mv_viewpager.adapter!!.instantiateItem(mv_viewpager, 1) as Fragment
if ( fragment is ListShowFragment)
fragment.onQueryTextChange(newText)
}
return true
}
})
return super.onCreateOptionsMenu(menu)
}
...
}
In my Activity I have:
int currentPage = 0;//start at the first tab
private SparseArray<Fragment> fragments;//list off fragments
viewPager.setOnPageChangeListener(new OnPageChangeListener() {
#Override
public void onPageSelected(int pos) {
currentPage = pos;//update current page
}
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) {}
#Override
public void onPageScrollStateChanged(int arg0) {}
});
#Override
public void onAttachFragment(Fragment fragment) {
super.onAttachFragment(fragment);
if(fragment instanceof Fragment1)
fragments.put(0, fragment);
if(fragment instanceof Fragment2)
fragments.put(2, fragment);
if(fragment instanceof Fragment3)
fragments.put(3, fragment);
if(fragment instanceof Fragment4)
fragments.put(4, fragment);
}
Then I have the following method for getting the current fragment
public Fragment getCurrentFragment() {
return fragments.get(currentPage);
}
Override setPrimaryItem from your FragmentPagerAdapter: the object is the visible fragment:
#Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
if (mCurrentFragment != object) {
mCurrentFragment = (LeggiCapitoloFragment) object;
}
super.setPrimaryItem(container, position, object);
}
Simply get the current item from pager and then ask your adapter to the fragment of that position.
int currentItem = viewPager.getCurrentItem();
Fragment item = mPagerAdapter.getItem(currentItem);
if (null != item && item.isVisible()) {
//do whatever want to do with fragment after doing type checking
return;
}
To get current fragment - get position in ViewPager at public void onPageSelected(final int position), and then
public PlaceholderFragment getFragmentByPosition(Integer pos){
for(Fragment f:getChildFragmentManager().getFragments()){
if(f.getId()==R.viewpager && f.getArguments().getInt("SECTNUM") - 1 == pos) {
return (PlaceholderFragment) f;
}
}
return null;
}
SECTNUM - position argument assigned in public static PlaceholderFragment newInstance(int sectionNumber); of Fragment
getChildFragmentManager() or getFragmentManager() - depends on how created SectionsPagerAdapter
You can implement a BroadcastReceiver in the Fragment and send
an Intent from anywhere. The fragment's receiver can listen
for the specific action and invoke the instance's method.
One caveat is making sure the View component is already instantiated
and (and for some operations, such as scrolling a list, the ListView
must already be rendered).
This is the simplest hack:
fun getCurrentFragment(): Fragment? {
return if (count == 0) null
else instantiateItem(view_pager, view_pager.currentItem) as? Fragment
}
(kotlin code)
Just call instantiateItem(viewPager, viewPager.getCurrentItem() and cast it to Fragment. Your item would already be instantiated. To be sure you can add a check for getCount.
Works with both FragmentPagerAdapter and FragmentStatePagerAdapter!
You can declare an Array of fragment as register fragments
class DashboardPagerAdapter(fm: FragmentManager?) : FragmentStatePagerAdapter(fm!!) {
// CURRENT FRAGMENT
val registeredFragments = SparseArray<Fragment>()
override fun instantiateItem(container: ViewGroup, position: Int): Any {
val fragment = super.instantiateItem(container, position) as Fragment
registeredFragments.put(position, fragment)
return fragment
}
override fun getItem(position: Int): Fragment {
return when (position) {
0 -> HomeFragment.newInstance()
1 -> ConverterDashboardFragment.newInstance()
2 -> CartFragment.newInstance()
3 -> CustomerSupportFragment.newInstance()
4 -> ProfileFragment.newInstance()
else -> ProfileFragment.newInstance()
}
}
override fun getCount(): Int {
return 5
}
}
Then you can use it as
adapter?.let {
val cartFragment = it.registeredFragments[2] as CartFragment?
cartFragment?.myCartApi(true)
}
I tried the following:
int index = mViewPager.getCurrentItem();
List<Fragment> fragments = getSupportFragmentManager().getFragments();
View rootView = fragments.get(index).getView();

Android FragmentPagerAdapter can't change tag of fragment

I am developing a simple web beowser with dynamic tabs to allow user to add/remove them. I use FragmentPagerAdapter, ViewPager and TabLayout to do this. If I create 2 tabs (add 2 fragments to list) and then remove the second one, everything work well and I can add another fragment to list. But if I add 2 and then remove the firs one and try to add new fragment to list, I recieve exception:
java.lang.IllegalStateException: Can't change tag of fragment Page{162e3786 #1 id=0x7f0d007d android:switcher:2131558525:1}: was android:switcher:2131558525:1 now android:switcher:2131558525:0
I think thats because each fragment has unique id inside FragmentPagerAdapter. 2 fragments will have id's 0 and 1. If I remove the first item(id is 0) and then add new one(id will be 1). But item with id 1 is already exists! Is there a way to change id's programmatically or set id's when creating fragments? Thanks in advance!
My FragmentPagerAdapter:
public class PagerAdapter extends FragmentPagerAdapter {
private List<Page> fragments;
public PagerAdapter(FragmentManager fm, List<Page> pages) {
super(fm);
this.fragments = pages;
}
#Override
public Fragment getItem(int position) {
return this.fragments.get(position);
}
#Override
public int getCount() {
return this.fragments.size();
}
#Override
public int getItemPosition(Object object) {
int index = pages.indexOf(object);
if (index == -1){
return PagerAdapter.POSITION_NONE;
} else {
return index;
}
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
FragmentManager manager = ((Fragment) object).getFragmentManager();
FragmentTransaction trans = manager.beginTransaction();
trans.remove((Fragment) object);
trans.commit();
}
}
Finally, after a hours of search and tries I found the solution.
To fix that you need to override getItemId() method of FragmentPagerAdapter in which you have to return a unique id for each fragment.

How to add Fragment to first position in FragmentPagerAdapter

I am using FragmentPagerAdapter and a ViewPager to add custom Fragments EDIT: from my MainActivity (also sending a bunch of extra data based on a JSON response via bundle) and using swiping motions to move to the next Fragments in the List.
public class MyPagerAdapter extends FragmentPagerAdapter implements Serializable {
public List<Fragment> fragments;
public FragmentManager fm;
public MyPagerAdapter(FragmentManager fm) {
super(fm);
this.fm = fm;
this.fragments = new ArrayList<Fragment>();
}
#Override
public Fragment getItem(int position) {
return fragments.get(position);
}
#Override
public int getCount() {
return fragments.size();
}
}
Everything is working fine as long as I'm adding new Fragments by
using
MyPagerAdapter pageAdapter = new MyPagerAdapter(getSupportFragmentManager());
pager = (ViewPager)findViewById(R.id.myViewPager);
pageAdapter.fragments.add(new CustomFragment());
pager.setAdapter(pageAdapter);
But I can't find a proper way to add Fragments to the beginning of the List and swipe back.
I've tried both
pageAdapter.fragments.add(0, new CustomFragment());
as well as changing the FragmentPagerAdapters List to LinkedList and using
pageAdapter.fragments.addFirst(new CustomFragment());
and then refreshing the adapter by using
pageAdapter.notifyDataSetChanged();
and i keep getting the following exception:
java.lang.IllegalStateException: Can't change tag of fragment CustomFragment{2ead9520 #10 id=0x7f0a0002 android:switcher:2131361794:10}: was android:switcher:2131361794:10 now android:switcher:2131361794:11
The key methods no one has talked about yet is public int getItemPosition(Object) which is used to remap fragments to pages after they move and public long getItemId(int position) which must be overridden by a pager adapter that reorders fragments. The default implementation uses the position of the page as the id, so reordering confuses the FragmentPagerAdapter.
(I am leaving out the Serializable interface as it is irrelevant for the purposes of answering the question - How to reorder fragments in a FragmentPagerAdapter).
public class MyPagerAdapter extends FragmentPagerAdapter {
public List<Fragment> fragments;
public FragmentManager fm;
public MyPagerAdapter(FragmentManager fm) {
super(fm);
this.fm = fm;
this.fragments = new ArrayList<Fragment>();
}
void addFragmentAtPosition(int position, Fragment f) {
if(position == fragments.size())
fragments.add(f);
else
fragments.add(position, f);
notifyDataSetChanged();
}
void removeFragmentAtPosition(int position) {
Fragment f = fragments.remove(position);
if(f != null)
fm.beginTransaction().remove(f).commit();
notifyDataSetChanged();
}
#Override
public Fragment getItem(int position) {
return fragments.get(position);
}
#Override
public int getCount() {
return fragments.size();
}
#Override
public int getItemPosition(Object object){
/*
* called when the fragments are reordered to get the
* changes.
*/
int idx = fragments.indexOf(object);
return idx < 0 ? POSITION_NONE : idx;
}
#Override
public long getItemId(int position) {
/*
* map to a position independent ID, because this
* adapter reorders fragments
*/
return System.identityHashCode(fragments.get(position));
}
}
The key additions are the overrides of public int getItemPosition(Object) and public long getItemId(int). These allow the FragmentPagerAdapter to reposition the existing fragments and to identify the existing active fragments in the FragmentManager cache correctly.
You should not create and add fragments this way. Instead just instantiate the fragments in getItem and the adapter will take care of using them. just do this:
#Override
public Fragment getItem(int position) {
Fragment fragment = new CustomFragment();
fragments.add(fragment)
return fragment
}
I would suggest you don't keep a list of references to fragments since it is not necessary and you risk to create memory leaks.
What i would do is create the fragment only when required like this :
#Override
public Fragment getItem(int position) {
Fragment fragment = new MyFragment();
return fragment;
}
To solve your problem you should create the fragment based on your needs, for example if you have fragments of different class instances like for example one instance of MyFragment another one of YourFragment and so on, just keep a list which says which kind of fragment occupy that position.
For example:
myListMap = new HashMap<Integer, Integer>();
myListMap.put(position, type);
and then create the fragment on the fly:
#Override
public Fragment getItem(int position) {
Fragment fragment = null;
int type = ...find fragment type in that position ....
if(type == MYFRAGMENTTYPE) {
fragment = new MyFragment();
}
return fragment;
}
Don't know if you still need the answer, but I was trying to do something similar and just found the solution :)
You are receiving that exception because of your getItem function. You are returning the fragment in the position that the function receives, and this position is always the last position of the array because that would correspond to the last added fragment in a "typical" usage.
In your case, you want to add a new Fragment in the first position, so your getItem will return twice the same fragment and throw the exception.
To avoid this you need to create a public function into your Adapter and the index of the fragment you are adding, and then return this specific fragment.
PS.: I'm developing only in Kotlin for about 6 months now, so it can have some typos.
public int newFragmentIndex = 0;
public List<Fragment> fragments;
...
public void addFragmentAt(int index, Fragment fragment) {
newFragmentIndex = index;
pageAdapter.fragments.add(index, fragment);
notifyDataSetChanged();
}
public void addFragment(Fragment fragment) {
newFragmentIndex = fragments.size();
pageAdapter.fragments.add(fragment);
notifyDataSetChanged();
}
...
#Override
public Fragment getItem(int position) {
return fragments.get(newFragmentIndex);
}
To call this from your Activity, change your pageAdapter.fragments.add(new CustomFragment());
to
pageAdapter.fragments.addFragment(new CustomFragment());
And
pageAdapter.fragments.add(0, new CustomFragment());
to
pageAdapter.fragments.addFragmentAt(0, new CustomFragment());
Hope this helps you with your problem!

Getting the current Fragment instance in the viewpager

Below is my code which has 3 Fragment classes each embedded with each of the 3 tabs on ViewPager. I have a menu option. As shown in the onOptionsItemSelected(), by selecting an option, I need to update the fragment that is currently visible. To update that I have to call a method which is in the fragment class. Can someone please suggest how to call that method?
public class MainActivity extends ActionBarActivity {
ViewPager ViewPager;
TabsAdapter TabsAdapter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ViewPager = new ViewPager(this);
ViewPager.setId(R.id.pager);
setContentView(ViewPager);
final ActionBar bar = getSupportActionBar();
bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
//Attaching the Tabs to the fragment classes and setting the tab title.
TabsAdapter = new TabsAdapter(this, ViewPager);
TabsAdapter.addTab(bar.newTab().setText("FragmentClass1"),
FragmentClass1.class, null);
TabsAdapter.addTab(bar.newTab().setText("FragmentClass2"),
FragmentClass2.class, null);
TabsAdapter.addTab(bar.newTab().setText("FragmentClass3"),
FragmentClass3.class, null);
if (savedInstanceState != null) {
bar.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0));
}
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.addText:
**// Here I need to call the method which exists in the currently visible Fragment class**
return true;
}
return super.onOptionsItemSelected(item);
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("tab", getSupportActionBar().getSelectedNavigationIndex());
}
public static class TabsAdapter extends FragmentPagerAdapter
implements ActionBar.TabListener, ViewPager.OnPageChangeListener {
private final Context mContext;
private final ActionBar mActionBar;
private final ViewPager mViewPager;
private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>();
static final class TabInfo {
private final Class<?> clss;
private final Bundle args;
TabInfo(Class<?> _class, Bundle _args) {
clss = _class;
args = _args;
}
}
public TabsAdapter(ActionBarActivity activity, ViewPager pager) {
super(activity.getSupportFragmentManager());
mContext = activity;
mActionBar = activity.getSupportActionBar();
mViewPager = pager;
mViewPager.setAdapter(this);
mViewPager.setOnPageChangeListener(this);
}
public void addTab(ActionBar.Tab tab, Class<?> clss, Bundle args) {
TabInfo info = new TabInfo(clss, args);
tab.setTag(info);
tab.setTabListener(this);
mTabs.add(info);
mActionBar.addTab(tab);
notifyDataSetChanged();
}
#Override
public void onPageScrollStateChanged(int state) {
// TODO Auto-generated method stub
}
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
// TODO Auto-generated method stub
}
#Override
public void onPageSelected(int position) {
// TODO Auto-generated method stub
mActionBar.setSelectedNavigationItem(position);
}
#Override
public void onTabReselected(Tab tab, FragmentTransaction ft) {
// TODO Auto-generated method stub
}
#Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
Object tag = tab.getTag();
for (int i=0; i<mTabs.size(); i++) {
if (mTabs.get(i) == tag) {
mViewPager.setCurrentItem(i);
}
}
tabPosition = tab.getPosition();
}
#Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
// TODO Auto-generated method stub
}
#Override
public Fragment getItem(int position) {
TabInfo info = mTabs.get(position);
return Fragment.instantiate(mContext, info.clss.getName(), info.args);
}
#Override
public int getCount() {
return mTabs.size();
}
}
}
Suppose below is the fragment class with the method updateList() I want to call:
public class FragmentClass1{
ArrayList<String> originalData;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View fragmentView = inflater.inflate(R.layout.frag1, container, false);
originalData = getOriginalDataFromDB();
return fragmentView;
}
public void updateList(String text)
{
originalData.add(text);
//Here I could do other UI part that need to added
}
}
by selecting an option, I need to update the fragment that is
currently visible.
A simple way of doing this is using a trick related to the FragmentPagerAdapter implementation:
case R.id.addText:
Fragment page = getSupportFragmentManager().findFragmentByTag("android:switcher:" + R.id.pager + ":" + ViewPager.getCurrentItem());
// based on the current position you can then cast the page to the correct
// class and call the method:
if (ViewPager.getCurrentItem() == 0 && page != null) {
((FragmentClass1)page).updateList("new item");
}
return true;
Please rethink your variable naming convention, using as the variable name the name of the class is very confusing(so no ViewPager ViewPager, use ViewPager mPager for example).
public class MyPagerAdapter extends FragmentPagerAdapter {
private Fragment mCurrentFragment;
public Fragment getCurrentFragment() {
return mCurrentFragment;
}
//...
#Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
if (getCurrentFragment() != object) {
mCurrentFragment = ((Fragment) object);
}
super.setPrimaryItem(container, position, object);
}
}
First of all keep track of all the "active" fragment pages. In this case, you keep track of the fragment pages in the FragmentStatePagerAdapter, which is used by the ViewPager.
#Override
public Fragment getItem(int index) {
Fragment myFragment = MyFragment.newInstance();
mPageReferenceMap.put(index, myFragment);
return myFragment;
}
To avoid keeping a reference to "inactive" fragment pages, you need to implement the FragmentStatePagerAdapter's destroyItem(...) method:
#Override
public void destroyItem (ViewGroup container, int position, Object object) {
super.destroyItem(container, position, object);
mPageReferenceMap.remove(position);
}
and when you need to access the currently visible page, you then call:
int index = mViewPager.getCurrentItem();
MyAdapter adapter = ((MyAdapter)mViewPager.getAdapter());
MyFragment fragment = adapter.getFragment(index);
Where the MyAdapter's getFragment(int) method looks like this:
public MyFragment getFragment(int key) {
return mPageReferenceMap.get(key);
}
Hope it may help!
This is the only way I don't get NullPointerException for the instance variables of that particular fragment classes. This might be helpful for others who stuck at the same thing. In the onOptionsItemSelected(), I coded the below way:
if(viewPager.getCurrentItem() == 0) {
FragmentClass1 frag1 = (FragmentClass1)viewPager
.getAdapter()
.instantiateItem(viewPager, viewPager.getCurrentItem());
frag1.updateList(text);
} else if(viewPager.getCurrentItem() == 1) {
FragmentClass2 frag2 = (FragRecentApps)viewPager
.getAdapter()
.instantiateItem(viewPager, viewPager.getCurrentItem());
frag2.updateList(text);
}
FragmentStatePagerAdapter has public method with the name instantiateItem that return your fragment based on specified parameter values, this method has two parameters ViewGroup (ViewPager) and position.
public Object instantiateItem(ViewGroup container, int position);
Used this method to get specified position's fragment,
Fragment fragment = (Fragment) adaper.instantiateItem(mViewPager, position);
I know its too late but I have really simple ways of doing it,
// for fragment at 0 possition
((mFragment) viewPager.getAdapter().instantiateItem(viewPager, 0)).yourMethod();
getSupportFragmentManager().getFragments().get(viewPager.getCurrentItem());
Cast the instance retreived from above line to the fragment you want to work on with. Works perfectly fine.
viewPager
is the pager instance managing the fragments.
There are a lot of answers here that don't really address the basic fact that there's really NO WAY to do this predictably, and in a way that doesn't result you shooting yourself in the foot at some point in the future.
FragmentStatePagerAdapter is the only class that knows how to reliably access the fragments that are tracked by the FragmentManager - any attempt to try and guess the fragment's id or tag is not reliable, long-term. And attempts to track the instances manually will likely not work well when state is saved/restored, because FragmentStatePagerAdapter may well not call the callbacks when it restores the state.
About the only thing that I've been able to make work is copying the code for FragmentStatePagerAdapter and adding a method that returns the fragment, given a position (mFragments.get(pos)). Note that this method assumes that the fragment is actually available (i.e. it was visible at some point).
If you're particularly adventurous, you can use reflection to access the elements of the private mFragments list, but then we're back to square one (the name of the list is not guaranteed to stay the same).
by selecting an option, I need to update the fragment that is currently visible.
To get a reference to currently visible fragment, assume you have a reference to ViewPager as mPager. Then following steps will get a reference to currentFragment:
PageAdapter adapter = mPager.getAdapter();
int fragmentIndex = mPager.getCurrentItem();
FragmentStatePagerAdapter fspa = (FragmentStatePagerAdapter)adapter;
Fragment currentFragment = fspa.getItem(fragmentIndex);
The only cast used line 3 is valid usually. FragmentStatePagerAdapter is an useful adapter for a ViewPager.
Best way to do this, just call CallingFragmentName fragment = (CallingFragmentName) viewPager .getAdapter() .instantiateItem(viewPager, viewPager.getCurrentItem()); It will re-instantiate your calling Fragment, so that it will not throw null pointer exception and call any method of that fragment.
Current Fragment:
This works if you created a project with the fragments tabbar template.
Fragment f = mSectionsPagerAdapter.getItem(mViewPager.getCurrentItem());
Note that this works with the default tabbed activity template implementation.
I have used the following:
int index = vpPager.getCurrentItem();
MyPagerAdapter adapter = ((MyPagerAdapter)vpPager.getAdapter());
MyFragment suraVersesFragment = (MyFragment)adapter.getRegisteredFragment(index);
When we use the viewPager, a good way to access the fragment instance in activity is instantiateItem(viewpager,index). //index- index of fragment of which you want instance.
for example I am accessing the fragment instance of 1 index-
Fragment fragment = (Fragment) viewPageradapter.instantiateItem(viewPager, 1);
if (fragment != null && fragment instanceof MyFragment) {
((MyFragment) fragment).callYourFunction();
}
In my previous implementation I stored a list of child Fragments to be able to access them later, but this turned out to be a wrong implementation causing huge memory leaks.
I end up using instantiateItem(...) method to get current Fragment:
val currentFragment = adapter?.instantiateItem(viewPager, viewPager.currentItem)
Or to get any other Fragment on position:
val position = 0
val myFirstFragment: MyFragment? = (adapter?.instantiateItem(viewPager, position) as? MyFragment)
From documentation:
Create the page for the given position. The adapter is responsible for
adding the view to the container given here, although it only must
ensure this is done by the time it returns from
finishUpdate(ViewGroup).
FragmentStatePagerAdapter has a private instance variable called mCurrentPrimaryItem of type Fragment. One can only wonder why Android devs did not supplied it with a getter. This variable is instantiated in setPrimaryItem() method. So, override this method in such a way for you to get the reference to this variable. I simply ended up with declaring my own mCurrentPrimaryItem and copying the contents of setPrimaryItem() to my override.
In your implementation of FragmentStatePagerAdapter:
private Fragment mCurrentPrimaryItem = null;
#Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment)object;
if (fragment != mCurrentPrimaryItem) {
if (mCurrentPrimaryItem != null) {
mCurrentPrimaryItem.setMenuVisibility(false);
mCurrentPrimaryItem.setUserVisibleHint(false);
}
if (fragment != null) {
fragment.setMenuVisibility(true);
fragment.setUserVisibleHint(true);
}
mCurrentPrimaryItem = fragment;
}
}
public TasksListFragment getCurrentFragment() {
return (YourFragment) mCurrentPrimaryItem;
}
You can define the PagerAdapter like this then you will able to get any Fragment in ViewPager.
private class PagerAdapter extends FragmentPagerAdapter {
private final List<Fragment> mFragmentList = new ArrayList<>();
public PagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
return mFragmentList.get(position);
}
#Override
public int getCount() {
return mFragmentList.size();
}
public void addFragment(Fragment fragment) {
mFragmentList.add(fragment);
}
}
To get the current Fragment
Fragment currentFragment = mPagerAdapter.getItem(mViewPager.getCurrentItem());
After reading all comments and answers I am going to explain an optimal solution for this problem. The best option is #rik's solution, so my improvement is based on his.
Instead of having to ask each FragmentClass like
if(FragmentClass1){
...
if(FragmentClass2){
...
}
Create your own interface, and make your child fragments implement it, something like
public interface MyChildFragment {
void updateView(int position);
}
Then, you can initiate and update your inner fragments with
Fragment childFragment = (Fragment) mViewPagerDetailsAdapter.instantiateItem(mViewPager,mViewPager.getCurrentItem());
if (childFragment != null) {
((MyChildFragment) childFragment).updateView();
}
P.S. Be careful where you put that code, if you call insatiateItem before the system actually creates it the savedInstanceState of your child fragment will be null therefor
public void onCreate(#Nullable Bundle savedInstanceState){
super(savedInstanceState)
}
Will crash your app.
Good luck
I had the same issue and solved it using this code.
MyFragment fragment = (MyFragment) thisActivity.getFragmentManager().findFragmentById(R.id.container);
Just replace the name MyFragment with the name of your fragment and add the id of your fragment container.
This is more future-proof than the accepted answer:
public class MyFragmentPagerAdapter extends FragmentPagerAdapter {
/* ------------------------------------------------------------------------------------------ */
// region Private attributes :
private Context _context;
private FragmentManager _fragmentManager;
private Map<Integer, String> _fragmentsTags = new HashMap<>();
// endregion
/* ------------------------------------------------------------------------------------------ */
/* ------------------------------------------------------------------------------------------ */
// region Constructor :
public MyFragmentPagerAdapter(Context context, FragmentManager fragmentManager) {
super(fragmentManager);
_context = context;
_fragmentManager = fragmentManager;
}
// endregion
/* ------------------------------------------------------------------------------------------ */
/* ------------------------------------------------------------------------------------------ */
// region FragmentPagerAdapter methods :
#Override
public int getCount() { return 2; }
#Override
public Fragment getItem(int position) {
if(_fragmentsTags.containsKey(position)) {
return _fragmentManager.findFragmentByTag(_fragmentsTags.get(position));
}
else {
switch (position) {
case 0 : { return Fragment.instantiate(_context, Tab1Fragment.class.getName()); }
case 1 : { return Fragment.instantiate(_context, Tab2Fragment.class.getName()); }
}
}
return null;
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
// Instantiate the fragment and get its tag :
Fragment result = (Fragment) super.instantiateItem(container, position);
_fragmentsTags.put(position, result.getTag());
return result;
}
// endregion
/* ------------------------------------------------------------------------------------------ */
}
The scenario in question is better served by each Fragment adding its own menu items and directly handling onOptionsItemSelected(), as described in official documentation. It is better to avoid undocumented tricks.
If your pager is inside a Fragment then use this:
private fun getPagerCurrentFragment(): Fragment? {
return childFragmentManager.findFragmentByTag("android:switcher:${R.id.myViewPagerId}:${myViewPager.currentItem}")
}
Where R.id.myViewPagerId is the id of your ViewPager inside the xml Layout.
Based on what he answered #chahat jain :
"When we use the viewPager, a good way to access the fragment instance in activity is instantiateItem(viewpager,index). //index- index of fragment of which you want instance."
If you want to do that in kotlin
val fragment = mv_viewpager.adapter!!.instantiateItem(mv_viewpager, 0) as Fragment
if ( fragment is YourFragmentFragment)
{
//DO somthign
}
0 to the fragment instance of 0
//=========================================================================//
//#############################Example of uses #################################//
//=========================================================================//
Here is a complete example to get a losest vision about
here is my veiewPager in the .xml file
...
<android.support.v4.view.ViewPager
android:id="#+id/mv_viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="5dp"/>
...
And the home activity where i insert the tab
...
import kotlinx.android.synthetic.main.movie_tab.*
class HomeActivity : AppCompatActivity() {
lateinit var adapter:HomeTabPagerAdapter
override fun onCreate(savedInstanceState: Bundle?) {
...
}
override fun onCreateOptionsMenu(menu: Menu) :Boolean{
...
mSearchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
...
override fun onQueryTextChange(newText: String): Boolean {
if (mv_viewpager.currentItem ==0)
{
val fragment = mv_viewpager.adapter!!.instantiateItem(mv_viewpager, 0) as Fragment
if ( fragment is ListMoviesFragment)
fragment.onQueryTextChange(newText)
}
else
{
val fragment = mv_viewpager.adapter!!.instantiateItem(mv_viewpager, 1) as Fragment
if ( fragment is ListShowFragment)
fragment.onQueryTextChange(newText)
}
return true
}
})
return super.onCreateOptionsMenu(menu)
}
...
}
In my Activity I have:
int currentPage = 0;//start at the first tab
private SparseArray<Fragment> fragments;//list off fragments
viewPager.setOnPageChangeListener(new OnPageChangeListener() {
#Override
public void onPageSelected(int pos) {
currentPage = pos;//update current page
}
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) {}
#Override
public void onPageScrollStateChanged(int arg0) {}
});
#Override
public void onAttachFragment(Fragment fragment) {
super.onAttachFragment(fragment);
if(fragment instanceof Fragment1)
fragments.put(0, fragment);
if(fragment instanceof Fragment2)
fragments.put(2, fragment);
if(fragment instanceof Fragment3)
fragments.put(3, fragment);
if(fragment instanceof Fragment4)
fragments.put(4, fragment);
}
Then I have the following method for getting the current fragment
public Fragment getCurrentFragment() {
return fragments.get(currentPage);
}
Override setPrimaryItem from your FragmentPagerAdapter: the object is the visible fragment:
#Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
if (mCurrentFragment != object) {
mCurrentFragment = (LeggiCapitoloFragment) object;
}
super.setPrimaryItem(container, position, object);
}
Simply get the current item from pager and then ask your adapter to the fragment of that position.
int currentItem = viewPager.getCurrentItem();
Fragment item = mPagerAdapter.getItem(currentItem);
if (null != item && item.isVisible()) {
//do whatever want to do with fragment after doing type checking
return;
}
To get current fragment - get position in ViewPager at public void onPageSelected(final int position), and then
public PlaceholderFragment getFragmentByPosition(Integer pos){
for(Fragment f:getChildFragmentManager().getFragments()){
if(f.getId()==R.viewpager && f.getArguments().getInt("SECTNUM") - 1 == pos) {
return (PlaceholderFragment) f;
}
}
return null;
}
SECTNUM - position argument assigned in public static PlaceholderFragment newInstance(int sectionNumber); of Fragment
getChildFragmentManager() or getFragmentManager() - depends on how created SectionsPagerAdapter
You can implement a BroadcastReceiver in the Fragment and send
an Intent from anywhere. The fragment's receiver can listen
for the specific action and invoke the instance's method.
One caveat is making sure the View component is already instantiated
and (and for some operations, such as scrolling a list, the ListView
must already be rendered).
This is the simplest hack:
fun getCurrentFragment(): Fragment? {
return if (count == 0) null
else instantiateItem(view_pager, view_pager.currentItem) as? Fragment
}
(kotlin code)
Just call instantiateItem(viewPager, viewPager.getCurrentItem() and cast it to Fragment. Your item would already be instantiated. To be sure you can add a check for getCount.
Works with both FragmentPagerAdapter and FragmentStatePagerAdapter!
You can declare an Array of fragment as register fragments
class DashboardPagerAdapter(fm: FragmentManager?) : FragmentStatePagerAdapter(fm!!) {
// CURRENT FRAGMENT
val registeredFragments = SparseArray<Fragment>()
override fun instantiateItem(container: ViewGroup, position: Int): Any {
val fragment = super.instantiateItem(container, position) as Fragment
registeredFragments.put(position, fragment)
return fragment
}
override fun getItem(position: Int): Fragment {
return when (position) {
0 -> HomeFragment.newInstance()
1 -> ConverterDashboardFragment.newInstance()
2 -> CartFragment.newInstance()
3 -> CustomerSupportFragment.newInstance()
4 -> ProfileFragment.newInstance()
else -> ProfileFragment.newInstance()
}
}
override fun getCount(): Int {
return 5
}
}
Then you can use it as
adapter?.let {
val cartFragment = it.registeredFragments[2] as CartFragment?
cartFragment?.myCartApi(true)
}
I tried the following:
int index = mViewPager.getCurrentItem();
List<Fragment> fragments = getSupportFragmentManager().getFragments();
View rootView = fragments.get(index).getView();

Is it possible to access the current Fragment being viewed by a ViewPager?

I have an app with a ViewPager and three Fragments. I'm trying to figure out how to get the current Fragment being viewed so I can get at its arguments.
I have an OnPageChangeListener grabbing the current page index, but
ViewPager.getChildAt(int position);
returns a View. What's the relationship between this View and the current Fragment?
I finally found an answer that worked for me. Basically, you can access the fragment for a viewPager page by using the tag "android:switcher:"+R.id.viewpager+":0".
I've solved this problem the other way round.
Instead of searching for the fragment from the activity, I'm registering the Fragment during it's onAttach() method at it's owner activity and de-registering it in the onStop() method.
Basic Idea:
Fragment:
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try{
mActivity = (IMyActivity)activity;
}catch(ClassCastException e){
throw new ClassCastException(activity.toString() +" must be a IMyActivity");
}
mActivity.addFragment(this);
}
#Override
public void onStop() {
mActivity.removeFragment(this);
super.onStop();
}
IMyActivity:
public interface IFriendActivity {
public void addFragment(Fragment f);
public void removeFragment(Fragment f);
}
MyActivity:
public class MyActivity implements IMyActivity{
[...]
#Override
public void addFragment(Fragment f) {
mFragments.add(f);
}
#Override
public void removeFragment(Fragment f) {
mFragments.remove(f);
}
}
Edit - Don't do this. If you're tempted to, read the comments for why it's a bad idea.
On the odd-chance you're still trying to solve this problem:
Extend FragmentPagerAdapter. In the constructor, build the Fragments you need and store them in a List (array/ArrayList) of Fragments.
private final int numItems = 3;
Fragment[] frags;
public SwipeAdapter (FragmentManager fm) {
super(fm);
//Instantiate the Fragments
frags = new Fragment[numItems];
Bundle args = new Bundle();
args.putString("arg1", "foo");
frags[0] = new MyFragment();
frags[1] = new YourFragment();
frags[2] = new OurFragment();
frags[2].setArguments(args);
}
Then for getItem(int position), you can do something like
public Fragment getItem(int position) {
return frags[position];
}
I'm not sure if this is the generally accepted way of doing it but it worked for me.
Edit
This is really not a good way to go. If you plan on handling orientation changes or your app going into the background, then this will probably break your code. Please read the comments below this answer for more info. Rather use #James 's answer
Yes, it's possible if you are using FragmentStatePagerAdapter.
ViewPager vp;
//...
YourFragment fragment = (YourFragment) adapter.instantiateItem(vp, vp.getCurrentItem());
PLEASE DON'T USE THIS
Make your adapter extend the following FragmentStatePagerWithCurrentAdapter class and instead of implementing getItem implement the same code into getItemAtIndex
Set the ViewPager OnPageChangeListener, to the instance of the adapter.
When you need to access the current Fragment you just call adapter.getCurrentItem().
package your.package;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.view.ViewPager.OnPageChangeListener;
import android.util.SparseArray;
import android.view.ViewGroup;
public abstract class FragmentStatePagerWithCurrentAdapter
extends FragmentStatePagerAdapter
implements OnPageChangeListener {
int currentPage = 0;
private SparseArray<Fragment> mPageReferenceMap = new SparseArray<Fragment>();
public FragmentStatePagerWithCurrentAdapter(FragmentManager fm) {
super(fm);
}
#Override
public final Fragment getItem(int index) {
Fragment myFragment = getItemAtIndex(index);
mPageReferenceMap.put(index, myFragment);
return myFragment;
}
public abstract Fragment getItemAtIndex(int index);
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
super.destroyItem(container, position, object);
mPageReferenceMap.remove(Integer.valueOf(position));
}
public Fragment getCurrentItem() {
return mPageReferenceMap.get(currentPage);
}
#Override
public void onPageScrollStateChanged(int arg0) {
}
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
}
#Override
public void onPageSelected(int newPageIndex) {
currentPage = newPageIndex;
}
}
I used as reference the following blog post: http://tamsler.blogspot.com/2011/11/android-viewpager-and-fragments-part-ii.html
It's been explained here : http://developer.android.com/guide/topics/fundamentals/fragments.html
In OnCreateView you must return a view to draw a UI for your fragment, I think that's the relationship.
Also this question might be similar: Get focused View from ViewPager
You can do so:
- On the class extent of a view pager adapter (such as PagerAdapter , FragmentStatePagerAdapter...) override method instantiateItem :
#Override
public Object instantiateItem(ViewGroup container, int position) {
final Fragment frag = (Fragment) super.instantiateItem(container, position);
if(frag instanceof ListNoteOfTypeFragment){
final ListNoteOfTypeFragment listNoteOfTypeFragment = (ListNoteOfTypeFragment) frag;
//do whatever you want with your fragment here
listNoteOfTypeFragment.setNoteChangeListener(mListener);
}
return frag;
}
Definitive answer that works seamlessly (but small hack):
somewhere in page fragment's layout:
<FrameLayout android:layout_width="0dp" android:layout_height="0dp" android:visibility="gone" android:id="#+id/fragment_reference">
<View android:layout_width="0dp" android:layout_height="0dp" android:visibility="gone"/>
</FrameLayout>
in fragment's onCreateView():
...
View root = inflater.inflate(R.layout.fragment_page, container, false);
ViewGroup ref = (ViewGroup)root.findViewById(R.id.fragment_reference);
ref.setTag(this);
ref.getChildAt(0).setTag("fragment:" + pageIndex);
return root;
and method to return Fragment from ViewPager, if exists:
public Fragment getFragment(int pageIndex) {
View w = mViewPager.findViewWithTag("fragment:" + pageIndex);
if (w == null) return null;
View r = (View) w.getParent();
return (Fragment) r.getTag();
}
Jorge Garcia's FragmentStatePagerWithCurrentAdapter is a very good solution but it needs a minor improvement. In case the activity gets destroyed and re-created in response to a configuration change or something like that the getItem will not be called for the fragments that were saved and retrieved by the fragment manager. So I override getItem normally in my subclass and I put the following in the FragmentStatePagerWithCurrentAdapter
#Override
public Object instantiateItem(ViewGroup container, int position) {
Object item = super.instantiateItem(container, position);
if ( item instanceof Fragment ) {
pageReferenceMap.put(position, (Fragment)item);
}
return item;
}
The instantiateItem is called every time the fragment in that position is accessed.
Or just save all Fragments in a map:
public class MyFragment extends Fragment implements OnPageChangeListener {
private ViewPager viewPager;
private FragmentStatePagerAdapter viewAdapter;
private View rootView;
private Map<Integer, Fragment> fragments = new HashMap<Integer, Fragment>();
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
rootView = inflater.inflate(R.layout.introdution, container, false);
viewPager = (ViewPager) rootView.findViewById(R.id.pager);
viewAdapter = new ViewAdapter(getFragmentManager());
viewPager.setAdapter(viewAdapter);
viewPager.addOnPageChangeListener(this);
return rootView;
}
private class ViewAdapter extends FragmentStatePagerAdapter {
public ViewAdapter(FragmentManager fragmentManager) {
super(fragmentManager);
}
#Override
public Fragment getItem(int position) {
Fragment result = null;
switch (position) {
case 0: result = Fragment1.newInstance(); break;
case 1: result = Fragment2.newInstance(); break;
}
if (result != null)
fragments.put(position, result);
return result;
}
#Override
public int getCount() {
return 2;
}
}
#Override
public void onPageScrollStateChanged(int arg0) {
}
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
}
#Override
public void onPageSelected(int position) {
Fragment currentFragment = fragments.get(position);
}
}
I think there is the better way by using this
Log.i(TAG, "getCurrentItem " + mViewPager.getCurrentItem());
Can get the current display fragment page.

Categories

Resources