Creating a Fragment every time a getItem() fires? - android

In the Android docs, there is a FragmentStatePageAdapter that instantiates a Fragment every time getItem fires. Is this sane? I've checked, and this fires every time I swipe, which means it creates a Fragment every time? Is this correct?
#Override
public Fragment getItem(int i) {
Fragment fragment = new DemoObjectFragment();
Bundle args = new Bundle();
// Our object is just an integer :-P
args.putInt(DemoObjectFragment.ARG_OBJECT, i + 1);
fragment.setArguments(args);
return fragment;
}
I'm pretty new to Android, so I just wanted a sanity check on this. It doesn't sound right.

This is normal with FragmentStatePagerAdapter.
As per the documentation:
This version of the pager is more useful when there are a large number
of pages, working more like a list view. When pages are not visible to
the user, their entire fragment may be destroyed, only keeping the
saved state of that fragment. This allows the pager to hold on to much
less memory associated with each visited page as compared to
FragmentPagerAdapter at the cost of potentially more overhead when
switching between pages.
Thus the FragmentStatePagerAdapter does all the heavy lifting to help you keep your memory footprint relatively low. To do this, it may destroy Fragments that are not visible.
In general, you can set the number of off-screen pages for a ViewPager to keep in memory with ViewPager.setOffscreenPageLimit().

Related

How to reuse Fragment instace in ViewPager?

I have a ViewPager in my app, this viewpager contains 10 same fragments with different arguments. Using FragmentStatePagerAdapter as the viewpager's adapter. FragmentStatePagerAdapter pre-make new instance when a page are selected and then destroy it. But I don't need 10 instances. 3 is enough. When user scroll to right, most left fragment can be reused, because GC are expensive. How to achieve it?
When you override the getItem function of the FragmentStatePagerAdapter, maintain a reference to each of the fragments, and instantiate them only if null:
private MyFragment myFragment0, myFragment1, myFragment2, ...;
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
if (myFragment0 == null) {
myFragment0 = new MyFragment();
}
return myFragment0;
case 1:
if (myFragment1 == null) {
myFragment1 = new MyFragment();
}
return myFragment1;
case 2:
if (myFragment2 == null) {
myFragment2 = new MyFragment();
}
return myFragment2;
...
}
}
Each of the 10 fragments will only be instantiated once, and each of the 10 fragments will only be instantiated once the user is 1 swipe away from viewing it.
Calling viewPager.setOffscreenPageLimit(10) like a different user suggested has a similar effect, but it actually results in instantiating all 10 fragments as soon as you set the adapter, which is likely to freeze the app for a short while and therefore not recommended.
As far as I know, unless your fragment instance has a ton of properties, destroying and creating the instances shouldn't be much of a problem. (Especially considering modern android devices with high power CPU and RAM)
But to answer your 'how to achieve' question...
You need a pool to manage the fragment instances. This will create, retain, and destroy (when necessary) the fragment instances.
Whenever ViewPagerAdapter.getItem() is called, get a fragment instance from the pool and return it.
If the pool has less than 3 instances, create one and return it. Otherwise, return an instance that is no longer used.
To determine an instance that is no longer used, keep track of which fragment instance represents which page, what the current page is, and which way the page is about to be viewed.
Now this is basic logic behind it, but is it really worth implementing all (especially #4).
I'm really sorry that I could not show you the actual code snippets since I've never tried to implement such way.
Hope it helps.

keep each fragment selected inner ViewPager?

I implement the same fragment of 100 in ViewPager on my application. The fragment rather large display data from the server. When I put the code mViewPager.setOffscreenPageLimit (100), the app feels very heavy. Therefore, I use the way: remove setOffscreenPageLimit and invoke a method to check the data on the server and displays it when the fragment was selected. But when this happens: when Fragment "1" was chosen, then to fragment "2", back to fragment "1", Fragment "1" will repeat the activity. My question is how to keep the activity of each fragment is selected or when returning to the fragment that was selected at the last condition? Sorry for my English.
setOffscreenPageLimit : Set the number of pages that should be
retained to either side of the current page in the view hierarchy in
an idle state. Pages beyond this limit will be recreated from the
adapter when needed.
Don't setOffscreenPageLimit too large, because the view pager will keep the page active so your app will run slowly. And when you remove setOffscreenPageLimit, this setting defaults to 1. It means that pages beyond this limit (1) will be recreated from the adapter when needed.
https://developer.android.com/reference/android/support/v4/view/ViewPager.html#setOffscreenPageLimit(int)
if you remove setOffscreenPageLimit, The way to keep the activity of each fragment is store the state data of fragment in:
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putData("key", fragmentData);
}
when you leave it, when you back again, you update the fragment by the state data you have stored.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
fragmentData = savedInstanceState.getData("key", defaultData);
}
You should use some fragments in a viewpager, do not use too more. think about use a fragment and display many views, data in it.

reuse fragments in FragmentStatePagerAdapter

MY PROBLEM
I am creating an Android app where the user swipes through a load of cards (like tinder)
Each card is VERY UI heavy (has a google map view and an admob ad)
I tried loading the list into a fragment adapter. This means a fragment for every single item. This is very memory intensive as I am creating about 30 google map views and 30 admob views for the 30 items.
My nexus 5 unsurprisingly crashes under this unnecessary heavy load.
My attempted solution
I have created 3 instances of the fragment.Loaded these fragments into a HashMap.
//for currently displayed fragment
key = 0
//for previous page
key = -1
for next page
key = 1
I keep the previous index.
On the getItem method of the adapter I detect if the user has gone to the previous page or the next page.
I get the associated fragment and return it.
My problem is I keep receiving exceptions like:
IllegalStateException: Fragment already added
I have tried detaching and/or removing the fragment but no luck :(
Any suggestions?
Thanks
From the Google Play ViewPager example...
private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
public ScreenSlidePagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
return new ScreenSlidePageFragment();
}
#Override
public int getCount() {
return NUM_PAGES;
}
}
In the getItem(int position) you have to create a new Fragment instance, old fragments should be destroyed when the setOffscreenLimit has been reached. I get the feeling originally you were creating all 30 Fragments at once?
take a look at android's effective navigation sample for proper use of fragmentstatepageradapter.
http://developer.android.com/shareables/training/EffectiveNavigation.zip
I recommend put your admob in the main activity below viewpager. so u dont need to restart it everytime. and google map inside swipe view is not recommended by google as it is hard to handle both swipe and map zoom features. So simply use static map api it provides a screenshot of map according to the coordinates u provide and implement an onclick method to launch map as another activity.

How bad would be to instance all the fragments of a ViewPager only once?

I use a ViewPager with a small and fixed number of views (just 3 or 4) in my MainActivity. If I follow the traditional way of implementing that ViewPager, I must do:
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return new MyFragment0();
case 1:
return new MyFragment1();
case 2:
return new MyFragment2();
}
return null;
}
In this approach, usually only the first and second fragments are instantiated, and, once the user swipes to the second or third tab, the third fragment is instantiated and maybe the first one is destroyed. The advantage is clear: Android only keeps in memory fragments that the user is directly interacting with. In most of my apps I do this and everything is OK, but nowadays I am developing an app that needs these fragments to interact with each other. For example, when a user clicks on a button on the third fragment, some function must be triggered on the first one. When same data is typed in the second fragment, another function must be executed on the third one and so on... My problem is that all the fragments are not instantiated all the time, so it's really painful the need to check if they are or not, and assuring the proper functions will be called once the involved fragments are instantiated (when the user swipes to them).
My question is: how bad would be to instantiate the three (on four) fragments only once and keep all of them on memory, so I can assure they are all instantiated all the time:
MyFragment0 myFragment0 = new MyFragment0();
MyFragment1 myFragment1 = new MyFragment1();
MyFragment2 myFragment2 = new MyFragment2();
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return myFragment0;
case 1:
return myFragment1;
case 2:
return myFragment2;
}
return null;
}
Depending upon the complexity of your Fragments, it's not bad at all, however I'd implement it using the native Offset of the ViewPager:
mViewPager.setOffscreenPageLimit(4);
This causes the ViewPager to set the number of pages that should be retained to either side of the current page in the view hierarchy in an idle state. So it will keep them in memory.
Experiment and see if it works. In any case, it's not bad to keep your fragments in memory, just make sure you're not reinstaciating them all the time unless it's needed.
Monitor your memory usage, and keep that under control are you're good to go.
Of course, if you instantiate a 20mb bitmap on each fragment… you're going to OOM the App.
On the other hand, design your app so that your Fragments might be destroyed. It can happen, and it's not under your total control. (Unless you leak memory). In the end, let Android do its job. ;)

ViewPager + FragmentStatePagerAdapter : need to know when fragment is displayed (selected)

I am using a ViewPager plugged to a FragmentStatePagerAdapter. Each fragment contains a listview. In this listview I wish to display ads and make calls to a analytics server. I only want to make those calls when the user navigates to a fragment (so I cannot use the onCreateView or onActivityCreated events in the Fragment). Is there an event I can hook on to this end ?
Update
I realized that the way I am fetching my current fragment is flawe
#Override
public void onPageSelected(int page) {
this.currentPage = page;
tabsAdapter.getItem(page);
}
getItem(int position) in the pager is actually responsible for instanciating the fragments, so it would create a new unattached fragment (hence getActivity() returning null). I think I will us an approach where I keep a map of the fragments (<Position, Fragment>), i will remove the fragment from the map when destoryItem is called in the pagerAdapter.
You are right, best option is the second solution here:
http://tamsler.blogspot.nl/2011/11/android-viewpager-and-fragments-part-ii.html
so your thoughts are correct.
How about this:
http://developer.android.com/reference/android/support/v4/view/ViewPager.OnPageChangeListener.html
The onPageSelected(int) method sounds like it does what you want.

Categories

Resources