Swipable and replacable tabs, Fragments and ActionBar - android

As topic states I'm trying to implement swipable tabs using Fragments and ActionBar. I've iplemented it successfully using ActionBarSherlock and code from examples with the TabsAdapter class (subclass to FragmentPageAdapter). Some of the fragments should be able to show a new tab in the same tab spor, and this is where the problem arises. I really can't figure out how to replace a fragment in the pager with a new one. I've googled and tried out a lot of stuff.
I can replacie the fragment in the internal collection holding the fragments classes inside the FragmentPageAdapter, thus making the new Fragment available to the getItem method but that doesn't do it as I can't figure out how to update the pager to show the new Fragment. Even after switching to another Tab and going back I still get the old Fragment which I suppose is being cached somewhere by the pager. So is there a way to notify an update to the pager?
Maybe I should just forget using FragmentPageAdapter ? In that case what is the right way to get all this functionallity happen?
P.S. If you want to see my code let me know, but it's basicly the same as in the examples and the problem is more general I think

When you use a ViewPager it keeps the visible page/fragment in memory in addition to pages/fragments that you can navigate to. In addition to this, if the ViewPager is using a FragmentPagerApdapter it keeps the views in memory for the life of the activity.
I answered a similar post to yours above removing a fragment which can be found here. Your case is an extension of this, removing and then adding something else.
https://stackoverflow.com/a/10399127/629555
Basically, you need to use a FragmentStatePagerAdapter, override the getItemPosition method to return PagerAdapter.POSITION_NONE and make sure you call .notifyDataSetChanged(); on your adapter when you want it changed.
The answer to the link above covers this in much more detail and provides a code example.

What about keeping a reference to the fragments in the adapter my self with the FragmentStatePagerAdapter ? Then I have them both saved in the memory but also have full control of deletion. Is there anything bad I'm missing with this approach and will I run into problems later on? Also I've changed so the fragments are passed on as objects and not classes, thus leaving the initiation outside the pager. Any problems with this?
Attaching my code of the adapter:
public static class TabsAdapter extends FragmentStatePagerAdapter implements
ActionBar.TabListener, ViewPager.OnPageChangeListener {
private final Context mContext;
private final ActionBar mActionBar;
private final ViewPager mViewPager;
private final ArrayList<Fragment> mTabs = new ArrayList<Fragment>();
public TabsAdapter(SherlockFragmentActivity 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, Fragment fragment) {
tab.setTag(fragment);
tab.setTabListener(this);
mTabs.add(fragment);
mActionBar.addTab(tab);
notifyDataSetChanged();
}
public void replaceTab(ActionBar.Tab tab, Fragment fragment, int position) {
tab.setTag(fragment);
tab.setTabListener(this);
mTabs.set(position, fragment);
notifyDataSetChanged();
}
#Override
public int getCount() {
return mTabs.size();
}
#Override
public Fragment getItem(int position) {
return mTabs.get(position);
}
#Override
public void onPageScrolled(int position, float positionOffset,
int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
mActionBar.setSelectedNavigationItem(position);
}
#Override
public void onPageScrollStateChanged(int state) {
}
#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);
}
}
}
#Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
}
#Override
public void onTabReselected(Tab tab, FragmentTransaction ft) {
}
#Override
public int getItemPosition(Object object){
return PagerAdapter.POSITION_NONE;
}
}

Related

FragmentStatePagerAdapter view pager fragments not showing after activity recreate

When my Activity gets recreated via orientation change or if "Don't keep activities" is turned on in developer settings, my ViewPager that has
FragmentStatePagerAdapter doesn't recreate fragments.
this is activity onCreate method:
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
AndroidInjection.inject(this);
super.onCreate(savedInstanceState);
ButterKnife.bind(this);
pagerAdapter = new CustomPagerAdapter(getSupportFragmentManager());
viewPager.setAdapter(pagerAdapter);
viewPager.addOnPageChangeListener(this);
presenter.onCreate();
}
this is FragmentStatePager:
public class CustomPagerAdapter extends FragmentStatePagerAdapter {
private List<Fragment> fragments = new ArrayList<>();
public MyFoodaPagerAdapter(FragmentManager fm) {
super(fm);
}
public void init(List<Fragment> initFrags) {
fragments.addAll(initFrags);
notifyDataSetChanged();
}
public void update(Fragment fragment, int position) {
fragments.set(position, fragment);
notifyDataSetChanged();
}
#Override
public Fragment getItem(int position) {
return fragments.get(position);
}
#Override
public int getItemPosition(Object object) {
if (!fragments.contains(object)) {
return POSITION_NONE; // replace
}
return super.getItemPosition(object);
}
public void clear(){
fragments.clear();
notifyDataSetChanged();
}
#Override
public int getCount() {
return fragments.size();
}
#Override
public CharSequence getPageTitle(int position) {
return "Title "+position; // TODO: 25/10/2017
}
}
Since FragmentStatePagerAdapter saves currently selected fragment and the one's right next to it, if I only have 2 fragments in pager and Activity gets recreated, changing fragment list and calling notifyDataSetChanged(); doesn't do anything.
getItem(int position) doesn't get triggered in PagerAdapter at all after activity gets recreated. If I have 3+ fragments in the pager and 1st one is selected then moving to 3rd page will render that fragment and moving back to 1st fragment will finally render 1st fragment. If I have only 2 fragments in pager then moving between does nothing, they do not get rendered.
Also what I'm seeing after Activity is recreated. FragmentManager has those 2 fragments with reference numbers lets say (Fragment1#1000) and (Fragment2#1001), when I add new fragments into private List<Fragment> fragments, (Fragment1#1050) and (Fragment2#1051) in pager adapter and call notifyDataSetChanged(), fragmentManager does not update its fragments, it will still have those old fragments in.
EDIT: I think I found my problem support FragmentPagerAdapter holds reference to old fragments
Which says in short, even in the comments, is to never hold reference to fragments outside of PagerAdapter.
Please use this code to update the Fragment View when you are moving between Fragments whether its two or more than that.
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser) {
getFragmentManager().beginTransaction().detach(this).attach(this).commit();
//getPlanListData();
}
}

How to pass data from an Activity to Tab Fragments using ViewPager?

Hey StackOverFlow community ! I really need help.. :(
I'm searching for 2 or 3 hours now and didn't find anything that is relevant and simple.
I explain the context :
I have an Activity A1. This activity contains 3 fragments F1, F2, F3. It uses a ViewPager so the 3 fragments are in facts selected either by selecting a tab or by swiping the screen and this works.
What I want to do now :
For some reasons, I call a Web Service for data in the activity. The data is dependent on the intent that the activity A1 gets from the original calling activity A0 (It's a group_id). So, I want to send this data that I get in A1 from the Web Service to each of my fragments F1, F2 and F3.
Do you have a solution or an explanation of how the to pass data to fragments in ViewPager ?
Thanks a lot!
There is the base code of my Activity A1 :
public class ShowGroupActivity extends FragmentActivity implements
ActionBar.TabListener {
private ViewPager viewPager;
private TabsPagerAdapter mAdapter;
private ActionBar actionBar;
// Tab titles
private String[] tabs = { "Infos", "Parcours", "Mur" };
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_ACTION_BAR);
setContentView(R.layout.activity_show_group);
// Initilization
viewPager = (ViewPager) findViewById(R.id.pager);
actionBar = getActionBar();
mAdapter = new TabsPagerAdapter(getSupportFragmentManager());
viewPager.setAdapter(mAdapter);
actionBar.setHomeButtonEnabled(false);
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
// Adding Tabs
for (String tab_name : tabs) {
actionBar.addTab(actionBar.newTab().setText(tab_name)
.setTabListener(this));
}
viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageSelected(int position) {
// on changing the page
// make respected tab selected
actionBar.setSelectedNavigationItem(position);
}
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
}
#Override
public void onPageScrollStateChanged(int arg0) {
}
});
#Override
public void onTabReselected(Tab tab, FragmentTransaction ft) {
}
#Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
// on tab selected
// show respected fragment view
viewPager.setCurrentItem(tab.getPosition());
}
#Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
}
Here is an example of a fragment : F1 (In which I want to display some data given by A1).
public class GroupInfoFragment extends Fragment {
public GroupInfoFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_group_info, container, false);
return v;
}
}
The easiest and fastest way to do it is by using EventBus.
Just post an event at your onResponse method in the activity and the fragment will handle the rest.
Activity-
Create a NetworkResponceEvent class
OnResponse called: post a new NetworkResponceEvent to the eventbus.
Fragment-
Register- onResume
UnRegister- osPause
Create method onEvent(NetworkResponceEvent e)- handle all of the
updates here.
You should use the getActivity() method from your fragments.
I would recommend declaring an interface inside your fragment, like this:
public class GroupInfoFragment extends Fragment {
...
public interface Callbacks {
// you should declare methods for returning the data you want form your activity here
Foo getData();
}
...
}
And then implementing this interface in your ShowGroupActivity class, like this:
public class ShowGroupActivity extends FragmentActivity implements
ActionBar.TabListener, GroupInfoFragment.Callbacks {
...
#Override
Foo getData() {
return mData;
}
...
}
Then you would be able to get the data you want from your fragment classes by calling:
GroupInfoFragment.Callbacks callbacks = (GroupInfoFragment.Callbacks) getActivity();
Foo data = callbacks.getData;
By doing this, you can implement your fragment class without worrying about the activity implementation.
Additional Notes
You can also override your fragment's onAttach() method so you can always guarantee that your fragment is being attached to an activity that implements the Callback interface. You can also keep a reference to the Callback instance:
Callbacks mCallbacks;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (!(activity instanceof Callbacks)) {
throw new ClassCastException("Activity must implement fragment's callbacks.");
}
mCallbacks = (Callbacks) activity;
}

Need some explanations about Android fragments and ActionBar tabs

I am trying to understand Android fragments and navigation, but there is something I just don't know how to do. I have created an app, with a MainActivity containing a viewPager :
public class MainActivity extends FragmentActivity implements TabListener
{
private ViewPager viewPager;
private TabsPagerAdapter mAdapter;
private ActionBar actionBar;
private String[] tabNames = {"Tab 1", "Tab 2", "Tab 3"};
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
viewPager = (ViewPager) findViewById(R.id.pager);
actionBar = getActionBar();
mAdapter = new TabsPagerAdapter(getSupportFragmentManager());
viewPager.setAdapter(mAdapter);
actionBar.setHomeButtonEnabled(false);
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
for(int i = 0 ; i < tabNames.length ; i++)
actionBar.addTab(actionBar.newTab().setText(tabNames[i]).setTabListener(this));
}
#Override
public void onTabReselected(Tab tab, FragmentTransaction ft) {}
#Override
public void onTabSelected(Tab tab, FragmentTransaction ft)
{
viewPager.setCurrentItem(tab.getPosition());
}
#Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {}
}
Here is TabsPagerAdapter :
public class TabsPagerAdapter extends FragmentPagerAdapter
{
public TabsPagerAdapter(FragmentManager fm)
{
super(fm);
}
#Override
public Fragment getItem(int index)
{
if(index == 0) return new FirstFragment();
else if(index == 1) return new SecondFragment();
else return new ThirdFragment();
}
#Override
public int getCount()
{
return 3;
}
}
And my FirstFragment is a list so it extends ListFragment. Here is what it looks like :
Now I want to go to another view if I click an item. Before I used to do it like this in apps without action bar and tabs :
Intent i = new Intent(this.getActivity().getApplicationContext(), MyNewActivity.class);
startActivity(i);
But now when I do this it doesn't display the action bar on top of the screen anymore, and I also want to keep the navigation state on this tab, if I go to another tab and then come back. What should I do?
Thanks for your help.
It is better to let each individual fragment manage its own menu items (actionbar) so you have to call setHasMenuOptions(true) in each fragment that you want to have menu options in. Get a reference to the actionbar in onActivityCreated() and configure your actionbar how you want it there. You will also have to override the oncreateoptionsmenu and onOptionsItemSelected in the fragment to handle menu item clicks.
Also using the view pager and tabs you want to make each tab a fragment. I don't know about making each tab an activity, and I don't even think that is possible, and if you are doing that then that is your problem. I don't see that from your code, and that is good.
Each tab needs to be a Fragment, so convert all of your activities into fragments and then use the supportFragmentManager to dynamically add and replace fragments to your framelayout resource, or override getItem and return the correct fragment as needed.

I don't want to refresh fragment on change

I have 3 fragments... with their 3 navigation tabs.
The problem is that if i'm on the first tab, and i click the third.. when I click another time the first, it reloads all the fragment. If I click the second tab (in the middle) and I click the first, it doesn't reload.
My objective is that I don't want to refresh my fragments never!
How can I do it?
My code is:
public class TabsPagerAdapter extends FragmentPagerAdapter {
public TabsPagerAdapter(FragmentManager fm) {
super(fm);
}
// Return Items
#Override
public Fragment getItem(int index) {
switch (index) {
case 0:
return new Retos();
case 1:
return new Amics();
case 2:
return new Ranking();
}
return null;
}
#Override
public int getCount() {
// get item count - equal to number of tabs
return 3;
}
}
And this :
public class Perfil extends ActionBarActivity implements ActionBar.TabListener{
private ViewPager viewPager;
private TabsPagerAdapter mAdapter;
private ActionBar actionBar;
private SearchView mSearchView;
private TextView mStatusView;
// Tab titles
private String[] tabs = { "TAB1", "TAB2", "TAB3" };
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.perfil);
// Initilization
viewPager = (ViewPager) findViewById(R.id.pager);
actionBar = getSupportActionBar();
mAdapter = new TabsPagerAdapter(getSupportFragmentManager());
viewPager.setAdapter(mAdapter);
actionBar.setHomeButtonEnabled(true);
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
/** Creating fragment1 Tab */
Tab tab = actionBar.newTab()
.setText("TAB1")
.setTabListener(this);
actionBar.addTab(tab);
/** Creating fragment2 Tab */
tab = actionBar.newTab()
.setText("TAB2")
.setTabListener(this);
//.setIcon(R.drawable.ic_action_group);
actionBar.addTab(tab);
/** Creating fragment3 Tab */
tab = actionBar.newTab()
.setText("TAB3")
.setTabListener(this);
actionBar.addTab(tab);
/**
* on swiping the viewpager make respective tab selected
* */
viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageSelected(int position) {
// on changing the page
// make respected tab selected
actionBar.setSelectedNavigationItem(position);
}
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
}
#Override
public void onPageScrollStateChanged(int arg0) {
}
});
}
#Override
public void onTabReselected(Tab tab, FragmentTransaction ft) {
}
#Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
// on tab selected
// show respected fragment view
viewPager.setCurrentItem(tab.getPosition());
}
#Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
}
}
viewPager.setOffscreenPageLimit(C_FRAGMENTS_TO_KEEP_IN_MEMORY);
C_FRAGMENTS_TO_KEEP_IN_MEMORY is the number of tabs at right and left of the current selected tab to keep in memory. So in your case should be 2.
Please be sure that you are NOT creating a new fragment instance each time you call the getItem method in you viewpager adapter.
Check the #Rarw answer in this page.
Try setting your ViewPager offScreenPageLimit to cover the number of fragments you are trying to load. OffScreenPageLimist keeps fragments alive in an idle state when they are not visible. I don't see that value set in your code and based on what you're describing, that fragments keep recycling, it sounds to me like you're using the default state of 1, which will only retain one of the fragments off screen and not both.
Some caveats, this approach only really works if you know in advance how many fragments you will need since if you're dynamically adding and removing fragments its hard to know how many if any to retain.
UPDATE
This is likely why you're pages are refreshing:
#Override
public Fragment getItem(int index) {
switch (index) {
case 0:
return new Retos();
case 1:
return new Amics();
case 2:
return new Ranking();
}
return null;
}
You keep returning a new fragment each time you switch id. What you should do something like this:
case 0:
if(mRetrosFragment == null)
mRetrosFragment = new Retros();
return mRetrosFragment;
This way you stop recreating the fragment every time the tab changes and instead retain that instance.

onCreateView not called for tabs in actionbar TabsAdapter for second time tab selected?

I have a tabbed application built with fragments and ActionBarSherlock. I have 3 tabs, with 3 ListFragment's Here's what's happening.
When I select any tab, the onCreate method for the associated fragment is called as expected at first time but not at second. The problem is that the onCreate method is called for the next adjacent tab but not selected tab.
Click on tab2 and onCreate of tab3 is called but not tab2.
Actually my requirement is, when i change some data in tab1 means fragment1. those changes are not effected in fragment2, when i select tab2 (fragment2) it means fragment2 onCreate() was not calling. why it's not refreshing the fragment properly. this is the adapter i am using.
private class TabsAdapter extends FragmentPagerAdapter implements ViewPager.OnPageChangeListener, ActionBar.TabListener {
public TabsAdapter(FragmentActivity activity, ActionBar actionBar, ViewPager pager) {
super(activity.getSupportFragmentManager());
mContext = activity;
mActionBar = actionBar;
mViewPager = pager;
mViewPager.setAdapter(this);
mViewPager.setOnPageChangeListener(this);
}
public void addTab(ActionBar.Tab tab, Class<?> clss, int tabId) {
mTabs.add(clss.getName());
mTabsId.add(tabId);
mActionBar.addTab(tab.setTabListener(this));
notifyDataSetChanged();
}
public Integer getIdForPosition(int position) {
if (position >= 0 && position < mTabsId.size()) {
return mTabsId.get(position);
}
return null;
}
#Override
public int getCount() {
return mTabs.size();
}
#Override
public Fragment getItem(int position) {
//TabInfo info = mTabs.get(position);
return Fragment.instantiate(mContext, mTabs.get(position), new Bundle());
}
#Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
Log.i(TAG, "*******tab selected*******" +tab);
clearDetails();
if (mViewPager.getCurrentItem() != tab.getPosition()) {
mViewPager.setCurrentItem(tab.getPosition(), true);
}
}
#Override
public void onPageSelected(int position) {
mActionBar.setSelectedNavigationItem(position);
if (mCurrentPosition == position) {
}
mNextPosition = position;
}
#Override
public void onTabReselected(Tab tab, FragmentTransaction ft) {
}
#Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
}
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
From this page:
The fragment of each page the user visits will be kept in memory,
though its view hierarchy may be destroyed when not visible.
This means that your fragments that are not visible to the user are still being kept in memory, so their onCreate methods won't be called when they're redisplayed. You can force them to be kicked out of memory when you switch pages by setting the ViewPager's offscreen page limit to 0.
A better way might be to use some sort of external data model shared between your Fragments and then use your onPageSelected method to tell the Fragment to update itself based on the data model when brought into view.
when you are on a Tab:(n), only Tab:(n-1) and Tab:(n+1) will be alive in the memory, for memory usage optimization. Rest all Tabs will be destroyed, thats the reason why when you come back to the first Tab, its onCreateView is being called again.
Actually Tab:1's onCreateView will be called even if you click Tab:2 because its the neighbourhood Tab.
One solution i got is:
change the OffscreenPageLimit of the ViewPager. Its default value is 1
Try changing it to 0. Should work.But in case if it didn't
Use the Call backs
#Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
}
#Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
}
#Override
public void onTabReselected(Tab tab, FragmentTransaction ft) {
}

Categories

Resources