I have following issue. I have ViewPager and FragmentPagerAdapter for it.
All I need to do is call some method from my Fragment which is in ViewPager when this page was selected.
I tried to keep all my Fragments in List inside my adapter, but the problem is that when I rotate my device Adapter use previous fragments. New fragments were created after rotation and I have list of them, but I don't now how to get access to previous fragments. I have references to new fragments only.
Here is my adapter:
public class MainPagerAdapter extends FragmentPagerAdapter {
private List<Fragment> fragmentList;
public MainPagerAdapter(FragmentManager fm) {
super(fm);
fragmentList = new ArrayList<Fragment>();
fragmentList.add(new TaskPageFragment());
fragmentList.add(new HistoryFragment());
fragmentList.add(new TestFragment());
}
#Override
public Fragment getItem(int i) {
return fragmentList.get(i);
}
#Override
public int getCount() {
return 3;
}
}
Of course when I trying to access to fragment which was only created like an object, I can't get access to Activity Context.
Fragment fragment = pagerAdapter.getItem(i);
if (fragment instanceof SelectionListener) {
((SelectionListener)fragment).onTabSelected();
}
Here is onTabSelected() method call is shown. It's my interface which I implemented to each fragment in ViewPager and when it gets called after screen rotation I get null Context
Device configurations can change during runtime (such as screen orientation). When this happens the Activity is restarted. This is done so your activities can adjust to match the new device configuration, and since fragments are within Activities they are also 'destroyed'.
However your issue may be solved with something simple, since from what I understand from your question you are not looking for UI configuration changes and assuming you are using API 11+.
public void setRetainInstance (boolean retain)
Since: API Level 11
Control whether a fragment instance is retained across Activity re-creation (such as from a configuration change). This can only be used with fragments not in the back stack. If set, the fragment lifecycle will be slightly different when an activity is recreated:
onDestroy() will not be called (but onDetach() still will be, because
the fragment is being detached from its current activity).
onCreate(Bundle) will not be called since the fragment is not being
re-created.
onAttach(Activity) and onActivityCreated(Bundle) will
still be called.
public MainPagerAdapter(FragmentManager fm) {
Fragment frag = null;
super(fm);
fragmentList = new ArrayList<Fragment>();
frag = new TaskPageFragment();
frag.setRetainInstance(true);
fragmentList.add(frag);
frag = new HistoryFragment();
frag.setRetainInstance(true);
fragmentList.add(frag);
frag = new TestFragment();
frag.setRetainInstance(true);
fragmentList.add(frag);
}
Related
I have a ViewPager which holds Fragments. ViewPager has an adapter of FragmentStatePagerAdapter
Adapter's getItem method.
#Override
public Fragment getItem(int position) {
String text= dbHelper.getText(position);
CustomFragment frg = new CustomFragment(text);
return frg;
}
I am initializing the fragment in the getItem method of the adapter.
Everything works perfectly.
When the orientation changes however, instead of restoring my initialized fragments, CustomFragments are created using the default constructor of CustomFragment. So this creates fragments with dummy data.
What is the reason of this?
How can i restore the previously created fragments?
The ideal way to initialise your Fragments is to create a factory method like:
public static CustomFragment newInstance(String text) {
Bundle arguments = new Bundle();
arguments.put("someText", text);
CustomFragment fragment = new CustomFragment();
fragment.setArguments(arguments);
return fragment;
}
and get the arguments with getArguments() in the onCreate() method and process it to initialise whatever you want in the fragment.
This way when your fragments are recreated on configuration change, the arguments are persisted and the Fragments take care of themselves when their onCreate() method is called.
You might have noticed the lint warnings about the same if you are using the latest tools.
I have a FragmentActivity which shows some fragments by ViewPager. Here's my code snippet,
MyFragmentActivity
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity);
viewPager = (ViewPager) findViewById(R.id.pager);
fetchDataAsync(this);
}
#Override
private void onDataReceived(List<String> data) {
viewPager.setAdapter(new MyFragmentPageAdapter(getChildFragmentManager()), data);
}
MyFragmentPagerAdapter
private class MyFragmentPagerAdapter extends FragmentPagerAdapter {
private Data data;
public MyFragmentPagerAdapter(FragmentManager fm, Data data)
{
super(fm);
this.data = data;
}
#Override
public Fragment getItem(int position) {
return MyFragment.newInstance(data.get(position));
}
#Override
public int getCount() {
return data.size();
}
#Override
public CharSequence getPageTitle(int position) {
return null;
}
}
The fragments are created dynamically by FragmentPagerAdapter and depend on data received.
My app crashed when screen rotation happened. With some search, I learned that "When a config change occurs the old Fragment isn't destroyed - it adds itself back to the activity when it's recreated". At that moment the data was null for recreated fragment so it's crashed at onCreateView.
Someone said FragmentStatePagerAdapter should work but not for my case. Another said reusing the fragment instead of recreating new ones, but ViewPager creates them automatically and how can I manage this. I want to permanently destroy the old fragments and not re-add them to activity, what could I do?
UPDATED
I removed constructor param from MyFragment, and passed it by setArgument
Bundle b = new Bundle();
b.putString("data", data.toString());
f.setArguments(b);
Now fragment can restore data while recreating since MyFragment could save state(data) in this way when config changes happened
You should handle the orientation change yourself.
It is covered by developer resources under configuration change, basically, this means adding to your AndroidManifest the attribute configChanges="orientation". Then when orientation changes, Activity.onConfigurationChanged will be called.
See for example Activity under Configuration Changes
Normally, this manual handle will result in your fragment being laid out, and so its size should change automatically.
Another solution, is to keep it like this : i mean, on orientation change, your
Activity is being recreated, and so is your Fragment.
So you need to ensure in your fragment that the data are available, eventually loading them if not.
Related Question.
I put together a simple app that goes like this:
Activity -> FirstFragment
Activity: onCreate() -> createFirstFragment()
FirstFragment firstFragment = (FirstFragment) getSupportFragmentManager().findFragmentByTag(FirstFragment.TAG);
if (firstFragment == null)
{
firstFragment = FirstFragment.newInstance();
getSupportFragmentManager()
.beginTransaction()
.add(R.id.firstFragmentContainer, firstFragment, FirstFragment.TAG)
.hide(firstFragment)
//.addToBackStack(null)
.commit();
}
Plain and simple, during onCreate() add and hide a fragment so that I can do show/hide animations later.
Now, my question is this: why does the Activity/FragmentManager not remember this transaction (regardless of whether I .addToBackStack() or setRetainInstance(true) on the fragment) when the activity is killed and recreated? You can test this by checking the Do not keep activities developer option. Start the app, firstFragment is hidden as expected, minimize and come back, and viola! firstFragment is there for all the world to see!
I would expect that this sort of thing would be managed by Android, or do I need to specifically record all my transactions and repeat them when the app is recreated?
Any help/advice would be greatly appreciated!
Edit: Also see related logged bug
Use FragmentStatePagerAdapter like below in your main activity. This internally calls 'onSaveInstanceState' of the fragments and hence keeps the track of the changes you made and retains the transactional states
class MyAdapter extends FragmentStatePagerAdapter {
public MyAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
// your code here
}
#Override
public int getCount() {
// returns no. of fragments count. in my case it is 4
return 4;
}
onCreate() in mainactivity generally looks like this:
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView (R.layout.scrollabletabs_main);
viewPager = (ViewPager) findViewById (R.id.pager);
FragmentManager fragManager = getSupportFragmentManager();
viewPager.setAdapter(new MyAdapter(fragManager));
}
From
http://developer.android.com/training/basics/fragments/fragment-ui.html it is mentioned that,
Note: When you remove or replace a fragment and add the transaction to the back stack, the fragment that is removed is stopped (not destroyed). If the user navigates back to restore the fragment, it restarts. If you do not add the transaction to the back stack, then the fragment is destroyed when removed or replaced.
I've an activity with two attributes:
private Fragment firstFragment;
private Fragment secondFragment;
In onCreate method:
adapter = new MyPagerAdapter(getSupportFragmentManager());
pager = (ViewPager) findViewById(R.id.pager);
pager.setAdapter(adapter);
pager.setOffscreenPageLimit(6);
pager.setSaveEnabled(true);
where MyPagerAdapter extends FragmentStatePagerAdapter class.
Into getItem() method:
#Override
public Fragment getItem(int position) {
Bundle args = new Bundle();
switch (position) {
case FIRST:
secondFragment = new FirstFragment();
secondFragment.setArguments(args);
return secondFragment;
case SECOND:
secondFragment = new SecondFragment();
secondFragment.setArguments(args);
return secondFragment;
}
}
and all works correctly.
But, when I change the screen orientation, the private attributes is set to null and I lost the reference of two fragments.
So i've tried to serialized this fragment with:
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
getSupportFragmentManager().putFragment(outState, FirstFragment.class.getName(), firstFragment);
getSupportFragmentManager().putFragment(outState, SecondFragment.class.getName(), secondFragment);
}
and load them into onCreate method with:
if (savedInstanceState != null) {
firstFragment = (FirstFragment) getSupportFragmentManager().getFragment(savedInstanceState, FirstFragment.class.getName());
secondFragment = (SecondFragment) getSupportFragmentManager().getFragment(savedInstanceState, SecondFragment.class.getName());
}
My questions:
1. Is it the correct way to serialized fragment into activity screen orientation changes?
2. Sometimes I've the error: "Unmarshalling unknown type code 55 at offset 448", is it possible that it has caused by fragment serialization?"
EDIT:
I need to have the fragments as activity attributes because I've a listener interface into activity that:
#Override
public void executeTask(String what) {
secondFragment.executeTask(what);
}
this method was invoked into firstFragment. So the FirstFragment can execute a method of SecondFragment.
I'm not sure what may be the cause of the problem but I'll give you a hint that may help.
I don't think that you should reference the Fragments in the Adapter from the Activity.
Mainly due to the fact that it's pretty hard to synchronize the Activity life-cycle, Fragment lifecycle and ViewPager children life-cycles. And if any bugs emerge the debugging can be really painful.
Believe me, been there, done that...
By the way - is there a reason why you need references to Fragments in your Activity ?
EDIT
I don't think you should pass the information between the Fragments this way. In general the FragmentManager handles (creates, deletes) Fragments on it's own and you cannot be sure that these Fragments will be available at any time.
I think that the best way would be to move your data to separate model (database entry, SharedPreference or a singelton class) and then letting know the Adapter that data has changed (by a DataObserver in Fragments or simply notify the Adapter to update children data by calling notifyDataChanged).
EXAMPLE
FragmentA --->listener (reloadData())--->Activity--->adapter.notifyDataChanged()-----> fragmentB gets updated
This way if you ever want to add a ThirdFragment or in fact any number of Fragments that will use the Data you will not have to worry about updating data in any of these - just let the Adapter worry about it.
If your using same layout for portrait and landscape then When orientation change you can avoid activity recreate. change your manifest as...
<activity android:name=".activity.MainActivity"
android:configChanges="keyboardHidden|orientation|screenSize">
</activity>
and then override onConfigurationChanged() in activity ...
#Override
public void onConfigurationChanged(Configuration newConfig)
{
super.onConfigurationChanged(newConfig);
}
I'm testing my app with 'destroy all activities as soon as user leaves it' to simulate the OS killing my app.
In my main activity, in onCreate, I instantiate all the fragments, add them to a list and then:
mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager(), fragments);
mViewPager = (ViewPager) super.findViewById(R.id.pager);
mViewPager.setAdapter(mSectionsPagerAdapter);
Here is the adapter code:
public class SectionsPagerAdapter extends FragmentPagerAdapter {
private List<Fragment> fragments;
public SectionsPagerAdapter(FragmentManager fragmentManager, List<Fragment> fragments) {
super(fragmentManager);
this.fragments = fragments;
}
#Override
public Fragment getItem(int position) {
return fragments.get(position);
}
#Override
public int getCount() {
return fragments.size();
}
#Override
public CharSequence getPageTitle(int position) {
switch (position) {
case 0: return getString(R.string.section_one);
case 1: return getString(R.string.section_two);
case 2: return getString(R.string.section_three);
case 3: return getString(R.string.section_four);
}
return null;
}
}
Now, the problem (crash) lies here, in an action bar item:
case R.id.action_refresh:
PostsFragment fragment = (PostsFragment) mSectionsPagerAdapter.getItem(mViewPager.getCurrentItem());
fragment.reloadDataFromServer();
Say the activity was opened, then destroyed on the back button. When I return to this activity and press the refresh action item, the app crashes. Why? When the activity is created again, it creates a new pager adapter, with all new 4 fragments. But onCreate of these fragments is never called, none of the members are initialized, so the reloadDataFromServer fails loading into a listview that hasn't been initialized yet.
HOWEVER, the onCreate of the previous referenced fragments IS called, but we no longer have a reference to them because we recreated the FragmentPagerAdapter with new fragments. This is driving me INSANE! Why is onCreate not called for the new fragments?
In case someone has problems with this, I'll explain my solution. Basically what happens is fragments that were created before are restored on recreation of the activity after a destruction. This may be a duh moment for some of you.
My problem is essentially having incorrect references to fragments.
First, I changed the setOffscreenPageLimit to the full (#tabs - 1). This means all fragments are created at the start and held in memory. I'll explain why this helps later.
This means in onSaveInstanceState, we can use putFragment on all 4 fragments with the outState Bundle.
In create, if savedInstanceState is null, we can create all 4 fragments from scratch. Otherwise, you can use getFragment on all 4 fragments and place them into the new pager adapter to hold as reference.
You may ask why I set setOffscreenPageLimit to max. I had problems with figuring out which fragments to save with putFragment. isAdded() did not seem to always work properly, neither did isResumed(), so it was easier to just save all of them because we know all 4 were created and were in the fragment manager at all times.