I know this question has been asked before but none of the answers given so far is of any help to me.
I have a viewpager which is populated with fragments (android.support.v4.app.Fragment) from a FragmentStatePagerAdapter . Some of these fragments contain logic that needs to be retained when the orientation changes, such as keeping track of which view is currently selected.
However, although I save the data in question in onSaveInstanceState the savedInstanceState is always null. I can solve this by storing the data in a static variable (which since I only have one instance of each fragment would work for me) but i found this to be a quite ugly solution and there has to be a proper way of doing this.
This is one of the fragments that doesn't retain it's state on rotation:
public class PriceSelectFragment extends Fragment {
private TableRow mSelected;
private int mSelectedPos = 0;
// newInstance constructor for creating fragment with arguments
public static PriceSelectFragment newInstance() {
PriceSelectFragment fragmentFirst = new PriceSelectFragment();
return fragmentFirst;
}
public PriceSelectFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_price_select, container, false);
TableLayout mTable = (TableLayout)view.findViewById(R.id.price_table);
List<PriceGroup> mPriceGroups = ((MainActivity) getActivity()).getPriceGroups();
int i = 0;
for (final PriceGroup group : mPriceGroups) {
//Create row from layout and access child TextViews
TableRow r = (TableRow)inflater.inflate( R.layout.price_group, mTable, false);
TextView size = (TextView)r.getChildAt(0);
TextView dimension = (TextView)r.getChildAt(1);
TextView weight = (TextView)r.getChildAt(2);
TextView price = (TextView)r.getChildAt(3);
//Populate row with PriceGroup Data
size.setText(group.sizeIndicator);
dimension.setText(String.format("%2.0fx%2.0fx%2.0f", group.length, group.width, group.height));
weight.setText(Float.toString(group.weight));
price.setText(Integer.toString(group.price));
//Alternate background color every other row
if (i % 2 == 0) {
r.setBackgroundDrawable(getResources().getDrawable(R.drawable.price_selector_1));
}
else {
r.setBackgroundDrawable(getResources().getDrawable(R.drawable.price_selector_2));
}
mTable.addView(r); // Add to table
r.setTag(i);
r.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
selectRow((TableRow) v);
}
});
i++;
}
mSelected = (TableRow)view.findViewWithTag(mSelectedPos);
selectRow(mSelected);
return view;
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("selected", mSelectedPos);
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null) {
mSelectedPos = savedInstanceState.getInt("selected");
}
}
private void selectRow(TableRow row) {
if ((int) mSelected.getTag() % 2 == 0) {
mSelected.setBackgroundDrawable(getResources().getDrawable(R.drawable.price_selector_1));
}
else {
mSelected.setBackgroundDrawable(getResources().getDrawable(R.drawable.price_selector_2));
}
mSelected = row;
mSelectedPos = (int) mSelected.getTag();
mSelected.setBackgroundColor(getResources().getColor(R.color.light_blue));
}
}
How do I solve this without having to save my states in static variables?
Edit
I should point out that all of the fragments are programatically created and as such they do not have an id and I read that that might be the problem but I don't know how to solve that either.
Also my application is structured like this:
MainActivity with NavigationDrawer
Fragment1
ViewPager
subfragment1 - subfragment5
Fragment2
Fragment3
The fragments whose states I'm having trouble with are the subfragments.
In your Activity which is hosting your Fragment you need to store a refernce to the fragment in the Bundle.
Something like this should work for you
public void onCreate(Bundle savedInstanceState) {
if (savedInstanceState != null) {
//Restore your fragment instance
fragment1 = getSupportFragmentManager().getFragment(
savedInstanceState, "fragment");
}
}
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
getSupportFragmentManager().putFragment(outState, "fragment", fragment1);
}
fragment1 is the instance of the Fragment1 that you mentioned in your question that needs to get recreated.
I haven't done this with a structure like yours before but this is how I would start:
In the onSaveInstanceState in your Fragment1 I believe you would need to do the same with each of the fragments in your ViewPager. Then in the onCreateView on your Fragment1 get the fragments from the fragment manager and recreate your ViewPager.
I have found this answer here which is pretty much the same but has a little more detail: https://stackoverflow.com/a/17135346/1417483
FragmentPagerAdapter is not calling onSaveInstanceState in frgments that are not visible anymore. Maybe this is what causing your issues.
Try to use FragmentStatePagerAdapter instead.
I finally got a solution and explanation why this is happening. I had a very similar problem. I recognized that when I was scrolling right to my 3rd subfragment and then back to the 1st then the state of the 1st got saved. But not on Orientation Change.
I figured that the state is only saved if the adapter's destroyItem(..) is called. That is not called automatically if orientation changes.
So now onSaveInstanceState of the MainFragment (which holds the ViewPager) I call destroyItem for each active fragment. I check for activity.isChangingConfigurations() because onSaveInstanceState is called too if I turn off the screen, but in that case all the fragments just stay active and nothing has to be changed.
I extended the adapter with an onDestroy(boolean retain) which is called then:
//in the main-fragment which holds the ViewPager:
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if(getActivity()!=null && getActivity().isChangingConfigurations())
{
if (pager != null) {
try {
Log.w(TAG, TAG + " pager.onDestroy(true)");
pager.onDestroy(true);
} catch (Exception e) {
e.printStackTrace();
}
}
And the implementation in MyFragmentStatePagerAdapter:
public void onDestroy(boolean retain)
{
this.m_allowDynamicLoading = false;
if(retain)
{
try{
if(getAdapter()!=null)
{
int limit = this.getOffscreenPageLimit();
int currentIndex = this.getCurrentItem();
if(currentIndex <0 || getAdapter().getCount() <= 0)
return;
//active fragments = fragments that are (less or equal) then
//offscreenPageLimit awaw from the currently displayed one.
for(int i = Math.min(currentIndex+limit, getAdapter().getCount()-1);
i>= Math.max(0, currentIndex-limit);//erstes aktives fragment ist current - offscreen limit, aber nicht unter 0..
i--)
{
getAdapter().destroyItem(MessagingViewPager.this, i, getAdapter().instantiateItem(MessagingViewPager.this, i)); //this saved the state of that fragment, that will be restored after orientation change
Log.e(TAG,TAG + " orientation-change: destroying item " + i);
}
}
}catch(Exception e){}
}
else{ //retain = false is called onDestroy of the Fragment holding this Pager.
try{
this.setAdapter(null);
//this will destroy all fragments and forget the position
}catch(Exception e){}
}
}
Some other things are to be said:
Adapter takes the ChildFragmentManager not the normal one
The SubFragments must NOT use setRetainInstance(true) (Exception otherwise)
The MainFragment can (and in my case does) use setRetainInstance(true)
Create the adapter in onCreate of the MainFragment, so it will NOT be recreated on Orientation change. Setting adapter to pager should be done in onCreateView.
OnDestroy (or onDestroyView) of the MainFragment use setAdapter(null) to terminate all fragments and release resources. (This is done by MyViewPager.onDestroy(false) in my case)
et voiá: now you get your savedInstanceState bundle in the SubFragments after the orientation change. And it will not destroy the items if you only switch the screen off.
Related
i update recycleView inside viewpager fragments and call pagerAdapter.notifyDataSetChanged(); to update them. It updates all recycleviews and the app takes long time. Now to overcome this problem i want to update only the current visible fragment and on ViewPage change update other fragments with notifyDataSetChanged(); is there a way to tell which fragment to update?
ViewPagerAdapter
private class MyPageAdapter extends FragmentStatePagerAdapter {
private List<Fragment> fragments;
private int[] mResources;
public MyPageAdapter(FragmentManager fm, List<Fragment> fragments) {
super(fm);
this.fragments = fragments;
}
#Override
public Fragment getItem(int position) {
return this.fragments.get(position);
}
#Override
public int getCount() {
return this.fragments.size();
}
#Override
public int getItemPosition(Object object) {
MyFragment f = (MyFragment) object;
if (f != null) {
f.update();
}
return super.getItemPosition(object);
}
}
this is fragment
public static class MyFragment extends Fragment implements CustomAdapterOdds.OnGameClickListener, Updateable {
public static final String COLUMN_IN_DISPLAY = "column_in_display";
RecyclerView recyclerView, recyclerView2;
HashMap<String, List<OddsFeed>> oddsList1;
HashMap<String, List<OddsFeed>> oddsList2;
int oddsColDisp;
CustomAdapterOdds adapterOdds1, adapterOdds2;
boolean isvisible = false;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
oddsList1 = new HashMap<>();
oddsList2 = new HashMap<>();
Bundle b = this.getArguments();
oddsColDisp = b.getInt(COLUMN_IN_DISPLAY);
List<GameFeed> leftGames = getArguments().getParcelableArrayList("list");
if (b.getSerializable("hashmap1") != null && b.getSerializable("hashmap2") != null) {
oddsList1 = (HashMap<String, List<OddsFeed>>) b.getSerializable("hashmap1");
oddsList2 = (HashMap<String, List<OddsFeed>>) b.getSerializable("hashmap2");
}
View v = inflater.inflate(R.layout.view_table_viewpager_fragment_layout, container, false);
ArrayList<GameFeed> gameFeedsCol0 = getArguments().getParcelableArrayList("list");
//recycleview1
recyclerView = v.findViewById(R.id.table_view_recycle_view_odds1);
adapterOdds1 = new CustomAdapterOdds(getContext(), leftGames, this, oddsList1, bestSitesList, oddsColDisp);
configRecyclerViewOdds(getContext(), recyclerView, adapterOdds1);
//recycleview2
recyclerView2 = v.findViewById(R.id.table_view_recycle_view_odds2);
adapterOdds2 = new CustomAdapterOdds(getContext(), leftGames, this, oddsList2, bestSitesList, oddsColDisp + 1, true);
configRecyclerViewOdds(getContext(), recyclerView2, adapterOdds2);
return v;
}
#Override
public void setMenuVisibility(final boolean visible) {
super.setMenuVisibility(visible);
if (visible) {
isvisible = true;
}
}
#Override
public void onResume() {
super.onResume();
Toast.makeText(getContext(), "onresume", Toast.LENGTH_SHORT).show();
adapterOdds1.updateListOdds(leftGames, oddsList1, oddsColDisp);
adapterOdds2.updateListOdds(leftGames, oddsList2, oddsColDisp + 1);
}
#Override
public void OnGameClickListener(View view, int position) {
}
#Override
public void update() {
Toast.makeText(getContext(), "update", Toast.LENGTH_SHORT).show();
if (isVisible()) {
adapterOdds1.updateListOdds(leftGames, oddsList1, oddsColDisp);
adapterOdds2.updateListOdds(leftGames, oddsList2, oddsColDisp + 1);
updateViewpager++;
int size = bestSitesList.size() / 2;
adapterOdds1.updateListOdds(leftGames, oddsList1, oddsColDisp);
adapterOdds2.updateListOdds(leftGames, oddsList2, oddsColDisp + 1);
if (updateViewpager == size - 1) {
progressBar.setVisibility(View.INVISIBLE);
updateViewpager = 0;
}
}
}
}
I'm sorry this is a bit of rambling answer but there was not enough code example given to make a functioning answer, so more really trying to explain a concept. Really need details of when and how the data is updated BUT...
As you are using androidx, you might want to consider moving to viewpager2 or with viewpager you can change how the fragment lifecycle states are managed.
If you changed to using BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT https://developer.android.com/reference/androidx/fragment/app/FragmentStatePagerAdapter#BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT when you constructed the Adapter https://developer.android.com/reference/androidx/fragment/app/FragmentStatePagerAdapter#FragmentStatePagerAdapter(androidx.fragment.app.FragmentManager,%20int)
Then all the Fragments will only be brought up to "Started" when created or re-created and only the current one on screen with "Resumed" and then "Paused" when moved off screen.
The exact mechanics depends on how the Fragments recyclerviews get their data and what triggers the update but then general idea is for the Fragments onCreateView to create a lightweight "shell" of a layout, I basically have the static buttons/text and create the recyclerview with an empty dataset.
Then in the onResume method of the Fragment which only gets called for the Fragment currently on Screen it calls your update method to replace the two recyclerview's empty datasets with the actual dataset and does a notifyDataSetChanged() on the recyclerviews.
Therefore when the viewpager is initially created X number of Fragments gets created and there static content is laid out plus 1 Fragment (the current one on screen) gets the recyclerview populated with actual data.
You might also then want to put in some optimisation checks in onResume of the Fragment to check the recyclerview views data has actually changed (a simple size check or using Recyclerview's DiffUtils) otherwise as you move between the Fragment's in the viewpager each Fragment will be paused/resumed.
This really only delays the cost of the two recyclerviews in the Fragment until it is really needed (when it is about to be displayed), it's a form of "Lazy loading"
With moving the "dynamic" data to this "lazy loading" is could be possible to remove the need to notifyDataSetChanged on the Fragments BUT the code snippets don't show enough about how and why the recyclerviews content changes.
With this method when the data is drawn is changed and you might not like how it looks.
This is really at a high level an inversion of the Fragments update logic, instead of saying "The data has changed redraw the data in all the Fragments" it is "This fragment is been shown, redraw the data IF it has been changed since the last time I drew it"
My fragment is being created twice, even though the activity is only adding the fragment once to the content. This happens when I rotate the screen. Also, everytime the fragment's onCreateView is called, it has lost all of it's variable state.
public class MainActivity extends ActionBarActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) { // Checking for recreation
getSupportFragmentManager().beginTransaction()
.add(R.id.container, new AppPanelFragment())
.commit();
}
}
}
onCreate in the activity checks for the null savedInstanceState and only if null will add the fragment so I can't see why the fragment should be created twice? Putting a breakpoint in that if condition tells me that it only ever get's called once so the activity shouldn't be adding the fragment multiple times. However the onCreateView of the fragment still gets called each time orientation changes.
public class AppPanelFragment extends Fragment {
private TextView appNameText;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// This method called several times
View rootView = inflater.inflate(R.layout.fragment_app_panel, container, false);
// 2nd call on this method, appNameText is null, why?
appNameText = (TextView) rootView.findViewById(R.id.app_name);
appNameText.text = "My new App";
return view;
}
I managed to have the variable state persisted using setRetainInstance(true), but is this the real solution? I would expect the fragment to not be created on just an orientation change.
In android, when the phone's orientation is changed, the activity is destroyed and recreated. Now, i believe to fix your problem you can use the fragment manager to check to see if the fragment already exists in the back stack and if it doesn't then create it.
public void onCreated(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
mFragmentManager = getSupportFragmentManager();
AppPanelFragment fragment = (AppPanelFragment)mFragmentManager.findFragmentById(R.id.fagment_id);
if(fragment == null) {
//do your fragment creation
}
}
}
P.S. I haven't tested this but it should work once you provide the right fragment's id in the findFragmentById method.
The Fragment lifecycle is very similar to an Activity. By default, yes, they will be re-created during a configuration change just like an Activity does. That's expected behavior. Even with setRetainInstance(true) (which I would say to use with extreme caution if it contains a UI) your View will be destroyed and re-created, but in that case your Fragment instance will not be destroyed -- just the View.
I know it is a bit late to answer, but using The Code Pimp answer you can do the next thing:
If the fragment exists in the backstack we pop and remove it to add it back (an exception is thrown if it is added back without removing it, saying it already exists).
The fragment variable is a class member variable.
This method will be called in the onCreate method of the Activity:
if (savedInstanceState == null) {
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
if (fragmentManager.findFragmentById(getFragmentActivityLayoutContainerId()) == null) {
fragment = getNewFragmentInstance();
} else {
fragment = fragmentManager.findFragmentById(getFragmentActivityLayoutContainerId());
fragmentTransaction.remove(fragment);
fragmentManager.popBackStack();
fragmentTransaction.commit();
fragmentTransaction = fragmentManager.beginTransaction();
}
fragmentTransaction.add(getFragmentActivityLayoutContainerId(), fragment);
fragmentTransaction.commit();
}
The next code will be called in the fragment itself.
It is a small example for a code you could implement in your fragment to understand how it works. The dummyTV is a simple text view in the center of the fragment that receives text according to orientation (and for that we need a counter).
private TextView dummyTV;
private static int counter = 0;
#Override
protected int getFragmentLayoutId() {
return R.layout.fragment_alerts_view;
}
#Override
protected void saveReferences(View view) {
dummyTV = (TextView) view.findViewById(R.id.fragment_alerts_view_dummy_tv);
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
if (savedInstanceState != null) {
dummyTV.setText(savedInstanceState.getString("dummy_string"));
} else {
dummyTV.setText("flip me!");
}
dummyTV.append(" | " + String.valueOf(counter));
}
#Override
public void onSaveInstanceState(Bundle outState) {
outState.putString("dummy_string", counter++ % 2 == 0 ? "landscape" : "portrait");
}
As mentioned, on orientation change, the activity is destroyed and recreated. Also, Fragments(any) are recreated by the system.
To ensure your application restores to previous state, onSaveInstanceState() is called before the activity is destroyed.
So, you can store some information in the onSaveInstanceState() method of an activity and then restore your application to same state on orientation change.
NOTE: You need not create fragments on orientation change, as fragments are recreated.
Example from http://www.mynewsfeed.x10.mx/articles/index.php?id=15:
public class MainActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if ( savedInstanceState == null ){
//Initialize fragments
Fragment example_fragment = new ExampleFragment();
FragmentManager manager = getFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.add(R.id.container, example_fragment, "Example");
} else{
//control comes to this block only on orientation change.
int postion = savedInstanceState.getInt("position"); //You can retrieve any piece of data you've saved through onSaveInstanceState()
//finding fragments on orientation change
Fragment example_fragment = manager.findFragmentByTag("Example");
//update the fragment so that the application retains its state
example_fragment.setPosition(position); //This method is just an example
}
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("position", position); //add any information you'd like to save and restore on orientation change.
}
}
I have a few fragments that are loaded when a user clicks on an item in a list. Say a user has clicked on second item in the list, loading the second fragment. But, upon rotating, the screen, the first fragment in the list gets loaded. How can I make sure that the same fragment gets loaded whenever a user rotates the screen.
This is how I'm loading my fragments
private void selectItem(position) {
Fragment fragment = null;
switch (position) {
case 0:
fragment = new FirstFragment();
break;
case 1:
fragment = new SecondFragment();
break;
case 2:
fragment = new ThirdFragment();
break;
default:
break;
}
if (fragment != null) {
android.app.FragmentManager fragmentManager = getFragmentManager();
fragmentManager.beginTransaction().replace(R.id.content_frame, fragment).commit();
}
else {
Log.e("NavigationActivity", "Error in creating fragment");
}
}
I'm calling selectItem(0) in onCreate of an activity.
The entire activity gets destroyed and recreated during a rotation. So if you are calling setItem(0) in Activity.onCreate, then you'll always get FirstFragment in the content frame.
Seems like the easy thing may be to just detect if you've already set a fragment in onCreate and not load the default. Either make use of onSaveInstanceState and/or mark the fragment as retained.
I don't have much experience with retained fragments or fragment management beyond initial load, so just using onSaveInstanceState to keep track of which one was loaded seems appropriate.
In your Activity, override onSaveInstanceState:
#Override
public void onSaveInstanceState(Bundle bundle)
{
bundle.putInt("which_fragment", _fragmentId);
super.onSaveInstanceState(bundle);
}
Where _fragmentId is just some numerical identifier of the particular fragment you are loading. It could even be it's layout id. Set this value in your selectItem method.
And then in onCreate:
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
_fragmentId = 0;
if (savedInstanceState != null)
{
_fragmentId = savedInstanceState.getInt("which_fragment", 0);
}
...
selectItem(_fragmentId);
}
First of all I wouldn't use positionOnTheList->Fragment dependency. I would depend on some id (final or from the resources).
Secondly I think you shouldn't create a new instance of each Fragment class when you select item from the list.
You should consider this approach:
Fragment f = fragmentManager.findFragmentById( String.valueOf(id) );
if( f == null )
f = new FragmentDependingOnId();
mCurrentlySelectedId = id;
fragmentManager.beginTransaction()
.replace( R.id.container, f , String.valueOf(id))
.commit();
Add the following method:
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(SELECTED_ID, mCurrentlySelectedId);
}
and in onCreate add:
if(savedInstanceState!=null){
mCurrentlySelectedId = savedInstanceState.getInt(SELECTED_ID);
selectItem(mCurrentlySelectedId);
}
When using fragment you usually use onCreateView to inflate your layout. Then you use onActivityCreated to do all the stuff you need to init listviews etc ...
In your case the problem you have is that you should use the saveInstanceState to keep track of if a fragment is loaded or not because the fragment is re-created on each rotation.
Let's look at some code
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.your_fragment_layout, container, false);
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null) {
//do nothing if the state already exists
} else {
//do something if state already exists
}
}
Note that if you need to save a given value, for example a boolean you can use
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(YOUR_BOOL_TAG, mYourBooleanVar);
}
and get it back in the onCreateView by using
mYourBooleanVar= savedInstanceState.getBoolean(YOUR_BOOL_TAG);
same applies to other types also.
EDIT
I didn't quite answered your question, so I put more details. The above code is in the fragment. However for your question, in the activity you need something like that.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
//here is the transaction to load your first fragment
}
}
and your first fragement won't reload each time.
The first time savedInstanceState will be null and you set your default fragment. Then each time you rotate savedInstanceState is not null and your default fragment is not reloaded but the one that is currently present.
Only this code is relevant for you, but I let the code above the EDIT for other people in case it can be useful to them.
Im having some problems when it comes to porting my app from the normal activity style to the fragment style. Im beginning to notice that when a fragment gets recreated, or popped from the backstack it loses its views. When I say that Im talking about a listview in particular. What im doing is im loading items into the listview, then rotating the screen. When it goes back through, it gets a nullpointerexception. I debug it and sure enough the listview is null. Here is the relevant code to the fragment
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup viewGroup, Bundle savedInstanceState) {
return inflater.inflate(R.layout.sg_question_frag, viewGroup, false);
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
list = (ListView)getActivity().findViewById(R.id.sgQuestionsList);
if (savedInstanceState != null) {
catId = savedInstanceState.getInt("catId");
catTitle = savedInstanceState.getString("catTitle");
}
populateList(catId, catTitle);
}
And here is how it is called (keep in mind there are a few other fragments that im working with as well)
#Override
public void onTopicSelected(int id, String catTitle) {
// TODO Auto-generated method stub
FragmentManager fm = this.getSupportFragmentManager();
SGQuestionFragment sgQuestFrag = (SGQuestionFragment) fm.findFragmentByTag("SgQuestionList");
FragmentTransaction ft = fm.beginTransaction();
//If the fragment isnt instantiated
if (sgQuestFrag == null) {
sgQuestFrag = new SGQuestionFragment();
sgQuestFrag.catId = id;
sgQuestFrag.catTitle = catTitle;
//Fragment isnt there, so we have to put it there
if (mDualPane) {
//TO-DO
//If we are not in dual pane view, then add the fragment to the second container
ft.add(R.id.sgQuestionContainer, sgQuestFrag,"SgQuestionList").commit();
} else {
ft.replace(R.id.singlePaneStudyGuide, sgQuestFrag, "SqQuestionList").addToBackStack(null).commit();
}
} else if (sgQuestFrag != null) {
if (sgQuestFrag.isVisible()) {
sgQuestFrag.updateList(id, catTitle);
} else {
sgQuestFrag.catId = id;
sgQuestFrag.catTitle = catTitle;
ft.replace(R.id.sgQuestionContainer, sgQuestFrag, "SgQuestionList");
ft.addToBackStack(null);
ft.commit();
sgQuestFrag.updateList(id, catTitle);
}
}
fm.executePendingTransactions();
}
What I would ultimately want it to do is to completely recreate the activity, forget the fragments and everything and just act like the activity was started in landscape mode or portrait mode. I dont really need the fragments there, I can recreate them progmatically with some saved variables
If you want to get a reference to a view from within a Fragment always look for that View in the View returned by the getView() method. In your case, at the time you look for the ListView the Fragment's view probably isn't yet attached to the activity so the reference will be null. So you use:
list = (ListView) getView().findViewById(R.id.sgQuestionsList);
Fragments seem to be very nice for separation of UI logic into some modules. But along with ViewPager its lifecycle is still misty to me. So Guru thoughts are badly needed!
Edit
See dumb solution below ;-)
Scope
Main activity has a ViewPager with fragments. Those fragments could implement a little bit different logic for other (submain) activities, so the fragments' data is filled via a callback interface inside the activity. And everything works fine on first launch, but!...
Problem
When the activity gets recreated (e.g. on orientation change) so do the ViewPager's fragments. The code (you'll find below) says that every time the activity is created I try to create a new ViewPager fragments adapter the same as fragments (maybe this is the problem) but FragmentManager already has all these fragments stored somewhere (where?) and starts the recreation mechanism for those. So the recreation mechanism calls the "old" fragment's onAttach, onCreateView, etc. with my callback interface call for initiating data via the Activity's implemented method. But this method points to the newly created fragment which is created via the Activity's onCreate method.
Issue
Maybe I'm using wrong patterns but even Android 3 Pro book doesn't have much about it. So, please, give me one-two punch and point out how to do it the right way. Many thanks!
Code
Main Activity
public class DashboardActivity extends BasePagerActivity implements OnMessageListActionListener {
private MessagesFragment mMessagesFragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
Logger.d("Dash onCreate");
super.onCreate(savedInstanceState);
setContentView(R.layout.viewpager_container);
new DefaultToolbar(this);
// create fragments to use
mMessagesFragment = new MessagesFragment();
mStreamsFragment = new StreamsFragment();
// set titles and fragments for view pager
Map<String, Fragment> screens = new LinkedHashMap<String, Fragment>();
screens.put(getApplicationContext().getString(R.string.dashboard_title_dumb), new DumbFragment());
screens.put(getApplicationContext().getString(R.string.dashboard_title_messages), mMessagesFragment);
// instantiate view pager via adapter
mPager = (ViewPager) findViewById(R.id.viewpager_pager);
mPagerAdapter = new BasePagerAdapter(screens, getSupportFragmentManager());
mPager.setAdapter(mPagerAdapter);
// set title indicator
TitlePageIndicator indicator = (TitlePageIndicator) findViewById(R.id.viewpager_titles);
indicator.setViewPager(mPager, 1);
}
/* set of fragments callback interface implementations */
#Override
public void onMessageInitialisation() {
Logger.d("Dash onMessageInitialisation");
if (mMessagesFragment != null)
mMessagesFragment.loadLastMessages();
}
#Override
public void onMessageSelected(Message selectedMessage) {
Intent intent = new Intent(this, StreamActivity.class);
intent.putExtra(Message.class.getName(), selectedMessage);
startActivity(intent);
}
BasePagerActivity aka helper
public class BasePagerActivity extends FragmentActivity {
BasePagerAdapter mPagerAdapter;
ViewPager mPager;
}
Adapter
public class BasePagerAdapter extends FragmentPagerAdapter implements TitleProvider {
private Map<String, Fragment> mScreens;
public BasePagerAdapter(Map<String, Fragment> screenMap, FragmentManager fm) {
super(fm);
this.mScreens = screenMap;
}
#Override
public Fragment getItem(int position) {
return mScreens.values().toArray(new Fragment[mScreens.size()])[position];
}
#Override
public int getCount() {
return mScreens.size();
}
#Override
public String getTitle(int position) {
return mScreens.keySet().toArray(new String[mScreens.size()])[position];
}
// hack. we don't want to destroy our fragments and re-initiate them after
#Override
public void destroyItem(View container, int position, Object object) {
// TODO Auto-generated method stub
}
}
Fragment
public class MessagesFragment extends ListFragment {
private boolean mIsLastMessages;
private List<Message> mMessagesList;
private MessageArrayAdapter mAdapter;
private LoadMessagesTask mLoadMessagesTask;
private OnMessageListActionListener mListener;
// define callback interface
public interface OnMessageListActionListener {
public void onMessageInitialisation();
public void onMessageSelected(Message selectedMessage);
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// setting callback
mListener = (OnMessageListActionListener) activity;
mIsLastMessages = activity instanceof DashboardActivity;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
inflater.inflate(R.layout.fragment_listview, container);
mProgressView = inflater.inflate(R.layout.listrow_progress, null);
mEmptyView = inflater.inflate(R.layout.fragment_nodata, null);
return super.onCreateView(inflater, container, savedInstanceState);
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// instantiate loading task
mLoadMessagesTask = new LoadMessagesTask();
// instantiate list of messages
mMessagesList = new ArrayList<Message>();
mAdapter = new MessageArrayAdapter(getActivity(), mMessagesList);
setListAdapter(mAdapter);
}
#Override
public void onResume() {
mListener.onMessageInitialisation();
super.onResume();
}
public void onListItemClick(ListView l, View v, int position, long id) {
Message selectedMessage = (Message) getListAdapter().getItem(position);
mListener.onMessageSelected(selectedMessage);
super.onListItemClick(l, v, position, id);
}
/* public methods to load messages from host acitivity, etc... */
}
Solution
The dumb solution is to save the fragments inside onSaveInstanceState (of host Activity) with putFragment and get them inside onCreate via getFragment. But I still have a strange feeling that things shouldn't work like that... See code below:
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
getSupportFragmentManager()
.putFragment(outState, MessagesFragment.class.getName(), mMessagesFragment);
}
protected void onCreate(Bundle savedInstanceState) {
Logger.d("Dash onCreate");
super.onCreate(savedInstanceState);
...
// create fragments to use
if (savedInstanceState != null) {
mMessagesFragment = (MessagesFragment) getSupportFragmentManager().getFragment(
savedInstanceState, MessagesFragment.class.getName());
StreamsFragment.class.getName());
}
if (mMessagesFragment == null)
mMessagesFragment = new MessagesFragment();
...
}
When the FragmentPagerAdapter adds a fragment to the FragmentManager, it uses a special tag based on the particular position that the fragment will be placed. FragmentPagerAdapter.getItem(int position) is only called when a fragment for that position does not exist. After rotating, Android will notice that it already created/saved a fragment for this particular position and so it simply tries to reconnect with it with FragmentManager.findFragmentByTag(), instead of creating a new one. All of this comes free when using the FragmentPagerAdapter and is why it is usual to have your fragment initialisation code inside the getItem(int) method.
Even if we were not using a FragmentPagerAdapter, it is not a good idea to create a new fragment every single time in Activity.onCreate(Bundle). As you have noticed, when a fragment is added to the FragmentManager, it will be recreated for you after rotating and there is no need to add it again. Doing so is a common cause of errors when working with fragments.
A usual approach when working with fragments is this:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
CustomFragment fragment;
if (savedInstanceState != null) {
fragment = (CustomFragment) getSupportFragmentManager().findFragmentByTag("customtag");
} else {
fragment = new CustomFragment();
getSupportFragmentManager().beginTransaction().add(R.id.container, fragment, "customtag").commit();
}
...
}
When using a FragmentPagerAdapter, we relinquish fragment management to the adapter, and do not have to perform the above steps. By default, it will only preload one Fragment in front and behind the current position (although it does not destroy them unless you are using FragmentStatePagerAdapter). This is controlled by ViewPager.setOffscreenPageLimit(int). Because of this, directly calling methods on the fragments outside of the adapter is not guaranteed to be valid, because they may not even be alive.
To cut a long story short, your solution to use putFragment to be able to get a reference afterwards is not so crazy, and not so unlike the normal way to use fragments anyway (above). It is difficult to obtain a reference otherwise because the fragment is added by the adapter, and not you personally. Just make sure that the offscreenPageLimit is high enough to load your desired fragments at all times, since you rely on it being present. This bypasses lazy loading capabilities of the ViewPager, but seems to be what you desire for your application.
Another approach is to override FragmentPageAdapter.instantiateItem(View, int) and save a reference to the fragment returned from the super call before returning it (it has the logic to find the fragment, if already present).
For a fuller picture, have a look at some of the source of FragmentPagerAdapter (short) and ViewPager (long).
I want to offer a solution that expands on antonyt's wonderful answer and mention of overriding FragmentPageAdapter.instantiateItem(View, int) to save references to created Fragments so you can do work on them later. This should also work with FragmentStatePagerAdapter; see notes for details.
Here's a simple example of how to get a reference to the Fragments returned by FragmentPagerAdapter that doesn't rely on the internal tags set on the Fragments. The key is to override instantiateItem() and save references in there instead of in getItem().
public class SomeActivity extends Activity {
private FragmentA m1stFragment;
private FragmentB m2ndFragment;
// other code in your Activity...
private class CustomPagerAdapter extends FragmentPagerAdapter {
// other code in your custom FragmentPagerAdapter...
public CustomPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
// Do NOT try to save references to the Fragments in getItem(),
// because getItem() is not always called. If the Fragment
// was already created then it will be retrieved from the FragmentManger
// and not here (i.e. getItem() won't be called again).
switch (position) {
case 0:
return new FragmentA();
case 1:
return new FragmentB();
default:
// This should never happen. Always account for each position above
return null;
}
}
// Here we can finally safely save a reference to the created
// Fragment, no matter where it came from (either getItem() or
// FragmentManger). Simply save the returned Fragment from
// super.instantiateItem() into an appropriate reference depending
// on the ViewPager position.
#Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
// save the appropriate reference depending on position
switch (position) {
case 0:
m1stFragment = (FragmentA) createdFragment;
break;
case 1:
m2ndFragment = (FragmentB) createdFragment;
break;
}
return createdFragment;
}
}
public void someMethod() {
// do work on the referenced Fragments, but first check if they
// even exist yet, otherwise you'll get an NPE.
if (m1stFragment != null) {
// m1stFragment.doWork();
}
if (m2ndFragment != null) {
// m2ndFragment.doSomeWorkToo();
}
}
}
or if you prefer to work with tags instead of class member variables/references to the Fragments you can also grab the tags set by FragmentPagerAdapter in the same manner:
NOTE: this doesn't apply to FragmentStatePagerAdapter since it doesn't set tags when creating its Fragments.
#Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
// get the tags set by FragmentPagerAdapter
switch (position) {
case 0:
String firstTag = createdFragment.getTag();
break;
case 1:
String secondTag = createdFragment.getTag();
break;
}
// ... save the tags somewhere so you can reference them later
return createdFragment;
}
Note that this method does NOT rely on mimicking the internal tag set by FragmentPagerAdapter and instead uses proper APIs for retrieving them. This way even if the tag changes in future versions of the SupportLibrary you'll still be safe.
Don't forget that depending on the design of your Activity, the Fragments you're trying to work on may or may not exist yet, so you have to account for that by doing null checks before using your references.
Also, if instead you're working with FragmentStatePagerAdapter, then you don't want to keep hard references to your Fragments because you might have many of them and hard references would unnecessarily keep them in memory. Instead save the Fragment references in WeakReference variables instead of standard ones. Like this:
WeakReference<Fragment> m1stFragment = new WeakReference<Fragment>(createdFragment);
// ...and access them like so
Fragment firstFragment = m1stFragment.get();
if (firstFragment != null) {
// reference hasn't been cleared yet; do work...
}
I found another relatively easy solution for your question.
As you can see from the FragmentPagerAdapter source code, the fragments managed by FragmentPagerAdapter store in the FragmentManager under the tag generated using:
String tag="android:switcher:" + viewId + ":" + index;
The viewId is the container.getId(), the container is your ViewPager instance. The index is the position of the fragment. Hence you can save the object id to the outState:
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("viewpagerid" , mViewPager.getId() );
}
#Override
protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.activity_main);
if (savedInstanceState != null)
viewpagerid=savedInstanceState.getInt("viewpagerid", -1 );
MyFragmentPagerAdapter titleAdapter = new MyFragmentPagerAdapter (getSupportFragmentManager() , this);
mViewPager = (ViewPager) findViewById(R.id.pager);
if (viewpagerid != -1 ){
mViewPager.setId(viewpagerid);
}else{
viewpagerid=mViewPager.getId();
}
mViewPager.setAdapter(titleAdapter);
If you want to communicate with this fragment, you can get if from FragmentManager, such as:
getSupportFragmentManager().findFragmentByTag("android:switcher:" + viewpagerid + ":0")
I want to offer an alternate solution for perhaps a slightly different case, since many of my searches for answers kept leading me to this thread.
My case
- I'm creating/adding pages dynamically and sliding them into a ViewPager, but when rotated (onConfigurationChange) I end up with a new page because of course OnCreate is called again. But I want to keep reference to all the pages that were created prior to the rotation.
Problem
- I don't have unique identifiers for each fragment I create, so the only way to reference was to somehow store references in an Array to be restored after the rotation/configuration change.
Workaround
- The key concept was to have the Activity (which displays the Fragments) also manage the array of references to existing Fragments, since this activity can utilize Bundles in onSaveInstanceState
public class MainActivity extends FragmentActivity
So within this Activity, I declare a private member to track the open pages
private List<Fragment> retainedPages = new ArrayList<Fragment>();
This is updated everytime onSaveInstanceState is called and restored in onCreate
#Override
protected void onSaveInstanceState(Bundle outState) {
retainedPages = _adapter.exportList();
outState.putSerializable("retainedPages", (Serializable) retainedPages);
super.onSaveInstanceState(outState);
}
...so once it's stored, it can be retrieved...
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState != null) {
retainedPages = (List<Fragment>) savedInstanceState.getSerializable("retainedPages");
}
_mViewPager = (CustomViewPager) findViewById(R.id.viewPager);
_adapter = new ViewPagerAdapter(getApplicationContext(), getSupportFragmentManager());
if (retainedPages.size() > 0) {
_adapter.importList(retainedPages);
}
_mViewPager.setAdapter(_adapter);
_mViewPager.setCurrentItem(_adapter.getCount()-1);
}
These were the necessary changes to the main activity, and so I needed the members and methods within my FragmentPagerAdapter for this to work, so within
public class ViewPagerAdapter extends FragmentPagerAdapter
an identical construct (as shown above in MainActivity )
private List<Fragment> _pages = new ArrayList<Fragment>();
and this syncing (as used above in onSaveInstanceState) is supported specifically by the methods
public List<Fragment> exportList() {
return _pages;
}
public void importList(List<Fragment> savedPages) {
_pages = savedPages;
}
And then finally, in the fragment class
public class CustomFragment extends Fragment
in order for all this to work, there were two changes, first
public class CustomFragment extends Fragment implements Serializable
and then adding this to onCreate so Fragments aren't destroyed
setRetainInstance(true);
I'm still in the process of wrapping my head around Fragments and Android life cycle, so caveat here is there may be redundancies/inefficiencies in this method. But it works for me and I hope might be helpful for others with cases similar to mine.
My solution is very rude but works: being my fragments dynamically created from retained data, I simply remove all fragment from the PageAdapter before calling super.onSaveInstanceState() and then recreate them on activity creation:
#Override
protected void onSaveInstanceState(Bundle outState) {
outState.putInt("viewpagerpos", mViewPager.getCurrentItem() );
mSectionsPagerAdapter.removeAllfragments();
super.onSaveInstanceState(outState);
}
You can't remove them in onDestroy(), otherwise you get this exception:
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
Here the code in the page adapter:
public void removeAllfragments()
{
if ( mFragmentList != null ) {
for ( Fragment fragment : mFragmentList ) {
mFm.beginTransaction().remove(fragment).commit();
}
mFragmentList.clear();
notifyDataSetChanged();
}
}
I only save the current page and restore it in onCreate(), after the fragments have been created.
if (savedInstanceState != null)
mViewPager.setCurrentItem( savedInstanceState.getInt("viewpagerpos", 0 ) );
What is that BasePagerAdapter? You should use one of the standard pager adapters -- either FragmentPagerAdapter or FragmentStatePagerAdapter, depending on whether you want Fragments that are no longer needed by the ViewPager to either be kept around (the former) or have their state saved (the latter) and re-created if needed again.
Sample code for using ViewPager can be found here
It is true that the management of fragments in a view pager across activity instances is a little complicated, because the FragmentManager in the framework takes care of saving the state and restoring any active fragments that the pager has made. All this really means is that the adapter when initializing needs to make sure it re-connects with whatever restored fragments there are. You can look at the code for FragmentPagerAdapter or FragmentStatePagerAdapter to see how this is done.
If anyone is having issues with their FragmentStatePagerAdapter not properly restoring the state of its fragments...ie...new Fragments are being created by the FragmentStatePagerAdapter instead of it restoring them from state...
Make sure you call ViewPager.setOffscreenPageLimit() BEFORE you call ViewPager.setAdapter(fragmentStatePagerAdapter)
Upon calling ViewPager.setOffscreenPageLimit()...the ViewPager will immediately look to its adapter and try to get its fragments. This could happen before the ViewPager has a chance to restore the Fragments from savedInstanceState(thus creating new Fragments that can't be re-initialized from SavedInstanceState because they're new).
I came up with this simple and elegant solution. It assumes that the activity is responsible for creating the Fragments, and the Adapter just serves them.
This is the adapter's code (nothing weird here, except for the fact that mFragments is a list of fragments maintained by the Activity)
class MyFragmentPagerAdapter extends FragmentStatePagerAdapter {
public MyFragmentPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
return mFragments.get(position);
}
#Override
public int getCount() {
return mFragments.size();
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
#Override
public CharSequence getPageTitle(int position) {
TabFragment fragment = (TabFragment)mFragments.get(position);
return fragment.getTitle();
}
}
The whole problem of this thread is getting a reference of the "old" fragments, so I use this code in the Activity's onCreate.
if (savedInstanceState!=null) {
if (getSupportFragmentManager().getFragments()!=null) {
for (Fragment fragment : getSupportFragmentManager().getFragments()) {
mFragments.add(fragment);
}
}
}
Of course you can further fine tune this code if needed, for example making sure the fragments are instances of a particular class.
To get the fragments after orientation change you have to use the .getTag().
getSupportFragmentManager().findFragmentByTag("android:switcher:" + viewPagerId + ":" + positionOfItemInViewPager)
For a bit more handling i wrote my own ArrayList for my PageAdapter to get the fragment by viewPagerId and the FragmentClass at any Position:
public class MyPageAdapter extends FragmentPagerAdapter implements Serializable {
private final String logTAG = MyPageAdapter.class.getName() + ".";
private ArrayList<MyPageBuilder> fragmentPages;
public MyPageAdapter(FragmentManager fm, ArrayList<MyPageBuilder> fragments) {
super(fm);
fragmentPages = fragments;
}
#Override
public Fragment getItem(int position) {
return this.fragmentPages.get(position).getFragment();
}
#Override
public CharSequence getPageTitle(int position) {
return this.fragmentPages.get(position).getPageTitle();
}
#Override
public int getCount() {
return this.fragmentPages.size();
}
public int getItemPosition(Object object) {
//benötigt, damit bei notifyDataSetChanged alle Fragemnts refrehsed werden
Log.d(logTAG, object.getClass().getName());
return POSITION_NONE;
}
public Fragment getFragment(int position) {
return getItem(position);
}
public String getTag(int position, int viewPagerId) {
//getSupportFragmentManager().findFragmentByTag("android:switcher:" + R.id.shares_detail_activity_viewpager + ":" + myViewPager.getCurrentItem())
return "android:switcher:" + viewPagerId + ":" + position;
}
public MyPageBuilder getPageBuilder(String pageTitle, int icon, int selectedIcon, Fragment frag) {
return new MyPageBuilder(pageTitle, icon, selectedIcon, frag);
}
public static class MyPageBuilder {
private Fragment fragment;
public Fragment getFragment() {
return fragment;
}
public void setFragment(Fragment fragment) {
this.fragment = fragment;
}
private String pageTitle;
public String getPageTitle() {
return pageTitle;
}
public void setPageTitle(String pageTitle) {
this.pageTitle = pageTitle;
}
private int icon;
public int getIconUnselected() {
return icon;
}
public void setIconUnselected(int iconUnselected) {
this.icon = iconUnselected;
}
private int iconSelected;
public int getIconSelected() {
return iconSelected;
}
public void setIconSelected(int iconSelected) {
this.iconSelected = iconSelected;
}
public MyPageBuilder(String pageTitle, int icon, int selectedIcon, Fragment frag) {
this.pageTitle = pageTitle;
this.icon = icon;
this.iconSelected = selectedIcon;
this.fragment = frag;
}
}
public static class MyPageArrayList extends ArrayList<MyPageBuilder> {
private final String logTAG = MyPageArrayList.class.getName() + ".";
public MyPageBuilder get(Class cls) {
// Fragment über FragmentClass holen
for (MyPageBuilder item : this) {
if (item.fragment.getClass().getName().equalsIgnoreCase(cls.getName())) {
return super.get(indexOf(item));
}
}
return null;
}
public String getTag(int viewPagerId, Class cls) {
// Tag des Fragment unabhängig vom State z.B. nach bei Orientation change
for (MyPageBuilder item : this) {
if (item.fragment.getClass().getName().equalsIgnoreCase(cls.getName())) {
return "android:switcher:" + viewPagerId + ":" + indexOf(item);
}
}
return null;
}
}
So just create a MyPageArrayList with the fragments:
myFragPages = new MyPageAdapter.MyPageArrayList();
myFragPages.add(new MyPageAdapter.MyPageBuilder(
getString(R.string.widget_config_data_frag),
R.drawable.ic_sd_storage_24dp,
R.drawable.ic_sd_storage_selected_24dp,
new WidgetDataFrag()));
myFragPages.add(new MyPageAdapter.MyPageBuilder(
getString(R.string.widget_config_color_frag),
R.drawable.ic_color_24dp,
R.drawable.ic_color_selected_24dp,
new WidgetColorFrag()));
myFragPages.add(new MyPageAdapter.MyPageBuilder(
getString(R.string.widget_config_textsize_frag),
R.drawable.ic_settings_widget_24dp,
R.drawable.ic_settings_selected_24dp,
new WidgetTextSizeFrag()));
and add them to the viewPager:
mAdapter = new MyPageAdapter(getSupportFragmentManager(), myFragPages);
myViewPager.setAdapter(mAdapter);
after this you can get after orientation change the correct fragment by using its class:
WidgetDataFrag dataFragment = (WidgetDataFrag) getSupportFragmentManager()
.findFragmentByTag(myFragPages.getTag(myViewPager.getId(), WidgetDataFrag.class));
A bit different opinion instead of storing the Fragments yourself just leave it to the FragmentManager and when you need to do something with the fragments look for them in the FragmentManager:
//make sure you have the right FragmentManager
//getSupportFragmentManager or getChildFragmentManager depending on what you are using to manage this stack of fragments
List<Fragment> fragments = fragmentManager.getFragments();
if(fragments != null) {
int count = fragments.size();
for (int x = 0; x < count; x++) {
Fragment fragment = fragments.get(x);
//check if this is the fragment we want,
//it may be some other inspection, tag etc.
if (fragment instanceof MyFragment) {
//do whatever we need to do with it
}
}
}
If you have a lot of Fragments and the cost of instanceof check may be not what you want, but it is good thing to have in mind that the FragmentManager already keeps account of Fragments.
add:
#SuppressLint("ValidFragment")
before your class.
it it doesn´t work do something like this:
#SuppressLint({ "ValidFragment", "HandlerLeak" })