I admit I am still struggling with Fragments and currently I don't know where to load what:
At first I loaded my Fragment in OnCreate() of my Activity, but then I had difficulty to access it (via findViewById) and moved it to onStart(), but I can't still find the Views within the Fragments on onStart()...
So in onCreate:
setContentView(R.layout.myfrag);
FrameLayout fl = (FrameLayout)findViewById(R.id.iPortraitView); // different for Landscape
if(fl.getChildCount() == 0) {
getFragmentManager().beginTransaction()
.add(R.id.iTestView, new AFragmentClass).commit();
}
else {
getFragmentManager().beginTransaction()
.replace(R.id.iTestView, new AFragmentClass).commit();
}
Log.d("myTest", String.valueOf(fl.getChildCount()));
Now the problem is that fl.getChildCount() always returns 0 in onCreate, thereby adding new Fragments, while actually there is something in there and when I call the same code (fl.getchildCount() ) in onCreate, I get the correct count (which obviously increases each time I change the orientation). And as a result I have overlapping Views from both my landscape and portrait layouts.
I'm a a loss here and guess I'm struggling with figuring out what to load when (and especially when I can access them). Furthermore, do I have to implement all Listeners for potential Views (like Buttons contained in the various Fragments) that I might load dynamically in my Fragments?
The Fragment's onCreateView() hasn't been called at that point in the Activity-Fragment life cycle. onCreate(Bundle bundle) is called before onCreateView(), so there would be no views to get the count of at that point of execution. See the lifecycle: http://developer.android.com/guide/components/fragments.html.
In order to set listeners, and other view modifications on your fragment, you need to overright the onCreateView() method within your fragment.
Related
I am adding and removing Views to/from my Activity dynamically. Each of these Views is assigned an id and acts as a container for a particular Fragment. I add a Fragment to each one of these Views with conditional logic as follows:
if (supportFragmentManager.findFragmentById(R.id.someView) == null) {
supportFragmentManager.beginTransaction()
.add(R.id.someView, SomeFragment())
.commit()
}
This conditional logic ensures that a given View only has a Fragment added to it once during the lifetime of the Activity.
This logic works fine except when the Activity is recreated (due to a configuration change for example). When the Activity is recreated, the Views are not automatically recreated but the Fragments appear to survive the recreation. (I see that the Fragments have survived the recreation because the supportFragmentManager.findFragmentById(id:) calls return a non-null Fragment.)
I find that if I re-add Views to my Activity in the Activity.onCreate(savedInstanceState:) method, then the retained Fragments re-attach fine to the Views and everything is fine. However, if I delay adding the Views to a later point in the Activity lifecycle, then the Fragments do not re-attach to the Views (and the Views show up as blank).
Ultimately, this leads to confusing logic in my Activity.onCreate(savedInstanceState:) method when savedInstanceState is non-null to work around this. Either I have to re-add Views as they were at the point when the Activity was destroyed (I would prefer to do this elsewhere in the Activity) or I have to call FragmentTransaction.remove(fragment:) to remove each Fragment which survived the recreation.
Is there a way to add a Fragment to an Activity such that the Fragment does not survive Activity recreation? I see in the deprecation notice for the Fragment.setRetainInstance(retain:) method that the guidance is: "Instead of retaining the Fragment itself, use a non-retained Fragment and keep retained state in a ViewModel attached to that Fragment." However, this guidance does not give any instruction on how to define a non-retained Fragment.
There are a couple of dimensions to this answer.
Firstly, I could not find any documentation or any methods in the FragmentManager or FragmentTransaction classes which offer a means of creating a non-retained Fragment. The documentation in the deprecated Fragment.setRetainInstance(retain:) method says to use a "non-retained Fragment" but I could not find anywhere that explains what this means.
Secondly, the workaround for this problem is to remove the retained Fragment in the containing Activity's onCreate(savedInstanceState:) method so that the problematic Fragment can be recreated and attached to its containing view in a later lifecycle method, as follows:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.some_activity)
supportFragmentManager.findFragmentById(R.id.someView)?.let {
supportFragmentManager.beginTransaction().remove(it).commit()
}
}
I had a layout for Fragment to show information about a product but sadly, during the creation of fragment there was slight lag( glitch) of around 50ms (that is what I guess how log the lag is which is a lot as the refresh rate for android is 16ms) but when I use the same layout in Activity directly and applied the same logic it looked and felt smooth.
Are there any particular reasons for this case ?
Are there any way to make fragment look as smooth as activity during creation ?
You can test some complex layout and try inflate it as fragment's view and using same as layout content for activity.
This is how my oncreate looks like in both fragment and activity:
#Override
public void onCreate ( Bundle savedInstanceState ) { // or equivalent override for fragment.
super.onCreate ( savedInstanceState );
setContentView ( R.layout.fragment_product_profile );
initLayout ();
loadData ();
initCustomMadeImageSlider ();
autoScrollViewPager ();
}
A fragment must always be embedded in an activity and the fragment's lifecycle is directly affected by the host activity's lifecycle.
Fragment : Major Advantage is
A separate Activity is created for each form factor with the non-UI
details duplicated or otherwise shared across each Activity
Fragments eliminate this problem by taking on the UI details and leaving the other responsibilities to the Activity. This way a separate Fragment can be created for each form factor with the form factor specific UI details being the only responsibilities of each Fragment.
Are Activity faster to create than fragment ? YES . Activity->Fragment .
A Fragment represents a behavior or a portion of user interface in an
Activity
Please read about When to use Fragments vs Activities
I have v4.ViewPager inside Activity and use SlidingTabLayout from google's examples SlidingTabBasics. The problem I encounter is that each fragment retrieved from getItem(position) in v4.FragmentPagerAdapter has to refresh activity title. I have already learnt the hard way that FragmentPagerAdapter causes fragments to have really weird life callbacks so I can't probably use onResume or onStart. I noticed though that onCreateOptionsMenu(menu,inflater) gets called exactly when I want to refresh activity title. Is there a callback to supply actions when ViewPager has settled the fragment and it should change activity title?
Setting callback on ViewPager.onPageSelected(position) is inconvenient because I want this information to be propagated from fragment, not to fragment.
Currently I 'steal' onCreateOptionsMenu(menu,inflater) to do the work for me but it causes optimisation issues when no menu should be inflated but I still want the fragment to be able to affect activity title.
Have you tried this code in your fragment:
if(getActivity() != null){
getActivity().setTitle("new title");
}
Take into consideration that getActivity() will be null if the fragment is not yet attached to the activity.
Godspeed.
You can do this in different ways.
You can use interfaces in fragments that can be implemented by activity. But the drawback is, if you do have large number of fragments you must implement all of them.
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!
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.