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);
}
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 ViewPager using a FragmentPagerAdapter for displaying three tabs, each represented by its ow fragment. One of these fragments contains a list, that should be updated on switching / swiping to that tab. But I don't find any way to make it happen. I tried using the onResume method, but the fragments seem not to be paused and resumed on tab change. I also tried using ViewPager.OnPageChangeListener in my MainActivity:
#Override
public void onPageSelected(int position)
{
FragmentRefreshInterface currentFragment = (FragmentRefreshInterface) mSectionsPagerAdapter.getItem(position);
currentFragment.onRefreshed();
}
And in the fragment I use the following:
#Override
public void onRefreshed()
{
List<Record> records = mRecordingService.getRecords();
mRecordAdapter.clear();
mRecordAdapter.add(record);
}
But using this code I can't access my RecordingService class that is used to provide the database functions (because mRecordingService seems to be null). I initialize it in the fragment like this:
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
mRecordingService = new RecordingService(getContext());
}
Using the onPageChangeListener is the correct way to do it. I believe the reason why your code is not working, is because you are calling getItem on your pager adapter: getItem() actually returns a new instance of the fragment. In order to get the current instance, you use instantiateItem() (which returns a reference to the fragment actually being used).
Change your code to look something like this:
#Override
public void onPageSelected(int position)
{
FragmentRefreshInterface currentFragment = (FragmentRefreshInterface) mSectionsPagerAdapter.instantiateItem(viewPager,position);
currentFragment.onRefreshed();
}
And it should work.
I suggest that the code you have in onRefreshed() go in onResume() instead. Fragment doesn't have an onRefreshed() method. You must be implementing another interface that declares this method.
Since you are storing data in a database, you should be use a CursorAdapter or subclass such as SimpleCursorAdapter. If you do this correctly, the ListView will automatically update when you add a record to the database. Then the service can add records without needing to access the service from the fragment.
In your MainActivity:
private FirstFragment firstFragment;
private WantedFragment wantedFragment;
private ThirdFragment thirdfragment;
In getItem
switch(postition){
//return first, wanted, third fragments depending on position
}
onPageSelected:
if(position == 1) // position of the wanted fragment
wantedfragment.onRefreshed()
Hi i created a project with a default "Navigation Drawer Activity".
So i have a MainActivity with a fragment with is replaced for each item on menu.
One of the menus is "Customers" with shows a list of customers.
From customers fragment i can see the Interests of this customers, with is a Fragment(CustomerListFragment) calling the interests(InterestsListFragment).
There is even more levels, but to be short that's enough.
This is the code on MainActivity that i use to call fragment from fragment and pass data between
public void passData(Object[] data, Fragment f) {
Bundle args = new Bundle();
args.putSerializable("PASSED_DATA", data);
f.setArguments(args);
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.container, f)
.addToBackStack("")
.commit();
}
And i use like :
mCallbacks.passData(new Object[]{c}, new OpportunityListFragment());
The problem is that when i rotate the phone does not matter from wich level of activity i have, it comes back to the first fragment called(CustomerListFragment), and if i click "Back" on cellphone it gets back to where i was when i rotate the phone.
What do i have to do, to avoid this kind of problem? why it gets back to the first activity evoked if i am replacing fragments?
The answer from ste-fu is correct but let's explore programmatically. There is a good working code in Google documentation # Handling Runtime Changes. There are 2 code snippets that you have to do.
1) Code snippet:
public class MyActivity extends Activity {
private RetainedFragment dataFragment;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// find the retained fragment on activity restarts
FragmentManager fm = getFragmentManager();
dataFragment = (DataFragment) fm.findFragmentByTag(“data”);
// create the fragment and data the first time
if (dataFragment == null) {
Note: Code uses FragmentManager to find the current Fragment. If fragment is null, then the UI or app has not been executed. if not null, then you can get data from RetainedFragment object.
2) Need to retain the Fragment state.
public class RetainedFragment extends Fragment {
// data object we want to retain
private MyDataObject data;
// this method is only called once for this fragment
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// retain this fragment
setRetainInstance(true);
}
Note: setRetainInstance is used in OnCreate. And subclassing the Fragment is recommended, naming it RetainedFragment, used on snippet 1.
When you change screen orientation your parent Activity is destroyed and recreated. Unless you persist the level structure in some fashion, it will always appear as when you first started the activity. You can either use the bundle object, or for more complicated objects you need to persist it to a database.
Either way, onSaveInstanceState is your friend. Then in your onCreate method you need to check the bundle or database, and the set the fragment accordingly.
I want to create a new instance of a Fragment in a PagerAdapter with some method like Fragment1.newInstance(0);
but this does not enter the Fragment onCreateView method, where I want to take its layout... How can I make it enter the lifecycle method?
EDIT: For the swipe tabs I am using an external library, that extends PagerAdapter, maybe thats the problem?
Everything you need is described here. See section "Adding a fragment to an activity" especially to solve your problem.
This is because ViewPager doesn't recreate fragment on each swipe .. it creates fragment only once and keeps in memory and recreate once after it goes offScreenpageLimit() ...For recreating fragment each time you swipe ,you have to set
mViewPager.setOffscreenPageLimit(0);
But this method is not recommended if you have so many pages in viewpager (atleast more than 10)..
in your pager adapter class return the instance like this
#Override
public Fragment getItem(int position){
return Fragment1.newInstance(position);
}
And in your Fragment create a static instance like this
public static NearbyOffersFragment newInstance(int position) {
Fragment1 f = new Fragment1();
Bundle b = new Bundle();
b.putInt("position", position);
f.setArguments(b);
return f;
}
and onCreate of your fragment retrieve the postion
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
position = getArguments().getInt("position");
}
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.