I initially create my fragments inside the Activity onCreate(). Than I go about creating the ViewPager and setting up the adapter. I keep a global reference to the fragments so I can update them as needed. Also, these fragments are accessed by the adapter.
My issue is that I notice once the screen is rotated the Fragments are recreated but the ViewPager still contains the original fragments created...??
How am I supposed to handle the life-cycle of my fragment? I need to be able to call directly to the fragment from the activity. Is a singleton for a fragment a good idea? Or just a memory leaker?
protected void onCreate (Bundle savedInstanceState)
{
...
...
// set up cards
mFrag1 = new Frag1();
mFrag1.setOnActionEventListener(mOnActionEvents);
mFrag2 = new Frag2();
mFrag3 = new Frag3();
mFragPager = (ViewPager) findViewById(R.id.vpPager);
mFragAdapter = new FragAdapter(getSupportFragmentManager());
mFragPager.setAdapter(mCardAdapter);
mFragPager.setOnPageChangeListener(mOnCardChange);
}
Global instances and static fragments are definitely a bad idea. Unless you call setRetainInstance() on a fragment, it will be serialized to a Bundle and recreated when an the parent activity is re-created (on screen rotate, etc.). That will, of course, produce new fragment instances, and your old references will be invalid. You should get references to fragments via FragmentManager.findFragmentById/Tag() as needed and definitely not store those in static variables.
You may need to show more of our code, but as long as you create the fragments in onCreate() the references should be valid for the lifetime of the activity. Check the compatibility library sample code for more on using fragments with ViewPager.
Related
My app has one MainActivity with three tabs (A, B, C).
Tab A shows FragmentA1. When I click a list entry in this fragment then FragmentA2 is shown (still in tab A). The same applies to the other tabs, some hierarchies go even deeper (FragmentC4).
All the switching and replacing of all the fragments is handled in MainActivity by Listeners. (Edit: I don't define my fragment in XML layouts but in the code only).
My Question is:
Should I hold references to all fragments in MainActivity or should I create them new everytime I need them?
What are the (dis)advantages? Can I reuse fragments by using Alternative 1, instead of recreating them everytime?
Alternative 1:
class MainActivity
private Fragment fgmtA1;
private Fragment fgmtA2;
private Fragment fgmtA3;
...
public onClickItemInA1(int itemId) {
fgmtA2 = new FragmentA2();
// put args
// replace
}
...
}
Alternative 2:
class MainActivity
...
public onClickItemInA1(int itemId) {
FragmentA2 fgmtA2 = new FragmentA2();
// put args
// replace
}
...
}
Alternative 3:
Maybe the best solution is a completely different approach?
Should I hold references to all fragments in MainActivity or should I
create them new everytime I need them?
It depends...
The only two reasons which i can think of are performance and keeping the state of a Fragment.
If you always create a new Fragment the GC will have a lot to do, which could cause some performance issues if you use a lot of bitmaps or huge data. You can reuse a Fragment by holding a reference to it in the Activity or getting the Fragment by tag or id using the methods FragmentManager.findFragmentByTag(String) or FragmentManager.findFragmentById(int). With them you can reuse already created Fragments, which should be done by default.
Furthermore if your Fragments hold some data, you will lose them or you cache it somewhere else to reacreate it if the Fragment is destroyed. While reusing a Fragment you can use onSavedInstanceState() to reacreate your state.
Hence, yes you should reuse a Fragment because it could cause system performance or headaches using anti-patterns to save some data.
In portrait mode, my ViewPager has 3 fragments A, B, C but in landscape mode, it has only 2 fragments A and C. So I create 2 FragmentStatePagerAdapters for each mode. The problem is when screen orientation changed, ViewPager restores and uses previous fragments of old orientation. For example, when change orientation from portrait to landscape, ViewPager now shows 2 fragments A, B instead of A and C. I know why this happen but can't find a good solution for this.
My current workaround is to use different ids for ViewPager (eg: id/viewpager_portrait for portrait and id/viewpager_landscape for landscape layout) to prevent from reusing fragments but this cause me a memory leak because old fragment will not be destroyed and still be kept in memory.
I have tried some workaround like call super.onCreate(null) in activity's onCreate, or remove fragments of ViewPager in activity's onSaveInstanceState but they all makes my app crash.
So my question is how to avoid reusing one or many fragments in FragmentStatePagerAdapter when orientation changed?
Any helps will be appreciated. Thank in advance.
The issue probably is that the built-in PagerAdapter implementations for Fragments provided by Android assume that the items will remain constant, and so retain and reuse index-based references to all Fragments that are added to the ViewPager. These references are maintained through the FragmentManager even after the Activity (and Fragments) is recreated due to configuration changes or the process being killed.
What you need to do is to write your own implementation of PagerAdapter that associates a custom tag with each Fragment and stores the Fragments in a tag-based (instead of index-based) format. You could derive a generic implementation of this from one of the existing ones after adding an abstract method for providing a tag based on the index alongside the getItem() method. Of course, you will have to remove orphaned/unused Fragments added in the previous configuration from the ViewPager (while ideally holding on to it's state).
If you don't want to implement the whole solution yourself, then the ArrayPagerAdapter in the CWAC-Pager library can be used to provide a reasonable implementation of this with little effort. Upon initialization, you can detach the relevant Fragment based on it's provided tag, and remove/add it from the adapter as well, as appropriate.
Override getItemPosition() in your Adapter and return POSITION_NONE. So when the ViewPager is recreated it will call getItemPosition() and since you've returned POSITION_NONE from here, it will call getItem(). You should return the new fragments in from this getItem(). Ref: http://developer.android.com/reference/android/support/v4/view/PagerAdapter.html#getItemPosition%28java.lang.Object%29
Why use two different ids for your viewpager, when you can just remove Fragment B when your orientation changes?
You can retrieve your Fragments inside onCreateView() or onResume() like this (this example works inside a parent fragment, but is also usable inside a parent activity like in onResume() ):
#Override
public void onResume() {
super.onResume();
// ... initialize components, etc.
pager.setAdapter(pagerAdapter);
List<Fragment> children = getChildFragmentManager().getFragments();
if (children != null) {
pagerAdapter.restoreFragments(children, orientation);
}
}
Then inside your adapter:
#Override
public void restoreFragments(List<Fragment> fragments, int orientation) {
List<Fragment> fragmentsToAdd = new ArrayList<Fragment>();
Collections.fill(fragmentsToAdd, null);
if (Configuration.ORIENTATION_LANDSCAPE == orientation) {
for (Fragment f : fragments) {
if (!(f instanceof FragmentB)) {
fragmentsToAdd.add(f);
}
}
}
this.fragmentsInAdapter = Arrays.copyOf(temp.toArray(new Fragment[0]), this.fragments.length); // array of all your fragments in your adapter (always refresh them, when config changes, else you have old references in your array!
notifyDataSetChanged(); // notify, to remove FragmentB
}
That should work.
Btw. if you're using Support Library V13, you can't use FragmentManager.getFragments(), thus you'll need to get them by id or tag.
Override OnConfigurationChanged() in your Activity (also add android:configChanges="orientation" to your activity in Manifest) this way you manage manually the the orientation change. Now "all" you have to do is to change the adapter in OnOrientaionChanged (also keep track of the current position). This way you use a single layout, a single ViewPager, and you don't have to worry about the fragment not getting recycled (well, you'll have plenty of work to make up for that).
Good luck!
First of all I'm sorry if this explanation seems unclear, I'm new to Android.
I have a ViewPager in main activity showing fragments added dynamically by user. Fragments are created initially on activity start up and are added to ViewPager via Adapter i.e. adapter simply returns proper fragment and as I understand correctly fragment's content is created at this time when ViewPager 'retrieves' a fragment first time.
The problem is when main activity gets restored after orientation changing all fragments are resurrected as well and when Adapter tries to return newly created by user Fragment method createView() is no longer called and it fails with NullPointerException. It seems ViewPager retains fragments attached to it initially and doesn't call createView() for newly added ones for the same position.
I have a feeling I'm missing vital point on the Fragment lifecycle. I wouldn't like to change the design. My main question is what the correct way is to return a Fragment added to ViewPage after activity is restored? Is there any way to locate recently attached fragments?
If the fragment already exists, it will be re-used. However the state of the fragment will not. You should take a look at http://developer.android.com/training/basics/activity-lifecycle/recreating.html for more information.
In particular you should look at overriding onSavedInstanceState and onRestoreInstanceState as a method to repopulate the field that is generating that nullpointerexception
To determine if an instance of a fragment has already been created you can use 'findFragmentByTag' like so:
String fragmentTag = MyFramgment.getClass().getName();
MyFragment frag = getFragmentManager().findFragmentByTag(fragmentTag);
if(frag == null)
frag = new MyFragment();
Whatever ends up being referenced in 'frag', show this to the user.
I'm Android programmer making first steps. In my app I decided to use ViewPager (support.v4).
I create all fragments in very typical way:
#Override
public void onCreate(Bundle savedInstanceState) {
(...)
//let's initialize view pager
List<Fragment> fragments = new ArrayList<Fragment>();
fragments.add(new MainActivitySelectDatabase());
fragments.add(new MainActivityDetails());
mainActivityPageAdapter = new MainActivityPageAdapter(getSupportFragmentManager(), fragments);
mainActivityViewPager = (ViewPager) findViewById(R.id.main_activity_view_pager);
mainActivityViewPager.setAdapter(mainActivityPageAdapter);
(...)
}
Next I would like to inform all fragments about one event in the following way:
protected void informAllFragmentsWhenDatabaseStatusChanged () {
for (int i=0; i<mainActivityPageAdapter.getCount(); i++) {
((InterfaceMainDatabaseStatusChanged) mainActivityPageAdapter.getItem(i)).onMainDatabaseStatusChanged();
}
}
And everything seems to work ok if I don't change screen orientation. When I change it everything becomes crazy. Methods like getActivity or getApplication in Fragment returns null. I'm guessing that for unknown reasons the method is executed for old version of my fragments, not for the new ones. How should it be solved? How to list current versions of all Fragments from main Activity?
Thanks in advance for help.
I'm guessing that for unknown reasons the method is executed for old
version of my fragments, not for the new ones.
Those unknown reasons are that the ViewPager only calls the getItem() method only when it doesn't have a valid instance for that page's fragment(and after a configuration change, the ViewPager will have references to those fragments through the FragmentManager). Your options are to get rid of the list of fragments and simply instantiate the proper fragment directly in the getItem() method(and also use a different method to access the fragments ) or to further extend the adapter so your list will always have the proper references to the fragments of the ViewPager.
For option one have a look at this question. You can also implement the communication by interfaces, registering each of the fragments of the ViewPager in their onAttach methods with the activity in some sort of WeakReference structure(and using this when needing to access the fragments).
To further extend the adapter have a look at the instantiateItem() method.
I'm building an app that the interface is based on this http://code.google.com/p/android-playground/, and I need to have a fragment inside of each tab (that are fragments). The tabs are all inflated from the same xml, and in that xml I have a fragment tag.
The problem is that when the activity is created, as the id of each fragment in the tabs are equal, the contents that should go to the second tab, go in the first.
I'm using this code to replace the fragment in the tab
FragmentTransaction ft = x.getSupportFragmentManager().beginTransaction();
ft.replace(R.id.details, fragment);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.commit();
I don't know if is possible to correct this.
do you want all the tabs to have the exactly the same content?
Usually when putting fragments in tabs each tab would be showing differnt content, so a xml file per tab would not be uncommon. You can have a seperate xml layout for each tab that just declares your fragment with a different id each time. Without declaring a seperate (read unique) id for each fragment there is no efficent / simple operation i know to get a handle to a specific fragment (as the id is the unique handle).
You also may be able to use a FragmentPagerAdapter depending on your needs. You could then fade your current tab fragment out, then call public void notifyDataSetChanged () and provide a new fragment. This is not really the standard way of doing it though and will not be preserved on the back stack.
Optionally you could create each tab programatically in the PagerAdapter and set a tag for each fragment when calling FragmentTransaction.add(..) and then use this tab-unique tag in future fragment transactions Ignore this, it does not look like you can switch fragements with a tag, id only im afraid. Go with my first suggestion I would!
I've struggled with ths as well and the way I reference a Fragment from its Activity later on is by accessing the Adapter for the tabs.
Assuming you use an Adapter, you could do something like this
private mTabsAdapter;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// etc
this.mTabsAdapter = new TabsAdapter(this, this.mViewPager);
// Tab1
Tab tab = actionBar.newTab();
this.mTabsAdapter.addTab(tab, MyFragment1.class, null);
// Tab2
tab = actionBar.newTab();
this.mTabsAdapter.addTab(tab, MyFragment2.class, null);
}
public MyFragment1 getTab1() {
return (MyFragment1)this.mTabsAdapter.getItem(0);
}
public MyFragment2 getTab2() {
return (MyFragment2)this.mTabsAdapter.getItem(1);
}
Then inside your fragment, you would have a method to access
public class MyFragment1 {
// ...
public void reloadData() {
// reload data here
}
}
Now you can access the Fragment such as
this.getTab1.reloadData();
This feels like a sad way to access a fragment, but you can't rely on a Tag in every scenario. Also, you must take care to only reference this where the reference to the Fragment exists.
There may be times that this will be null. For example if you nave a bunch of tabs, they may become garbage collected at some point. you should communicate between Fragments via a callback. This example is for only a few scenarios.
The callback method is described at Communicating with the Activity and I'd suggest this method if it fits your application. This will prevent the need for directly accessing tabs in most cases.