Get position of ViewPager after rotating in Android - android

I have a ViewPager. My FragmentPageAdapter returns the position of the Viewpager in the getItem() methode. But after rotating the Screen the methode returns no value. Why? If I understood it right, everytime you rotate the screen OnCreateView() is called, but why doesn't it return the value any more? Could someone point out how to solve this? Thank you
Edit: My FragmentPageAdapter:
public Fragment getItem(int position) {
return Fragment_results.newInstance(position);
}
My Fragment:
public static Fragment_results newInstance(int i) {
Fragment_results fragment = new Fragment_results();
fragment.mContent = i +"";
return fragment;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.layout_result, null);
((TextView) view.findViewById(R.id.text)).setText(mContent);

The position is set to 0 upon creating of the ViewPager instance and whenever you set a new adapter. When onCreateView() is called, you're rebuilding the entire app essentially. In order to revert back to the position it was, you must first use onSavedInstanceState(Bundle savedInstanceState) and store the item position via the Bundle.
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("pageItem", myViewPager.getCurrentItem());
}
Then in onCreate, restore the viewPager's state like so:
if (savedInstanceState != null) {
myViewPager.setCurrentItem(savedInstanceState.getInt("pageItem", 0));
}

Related

Fragments view reference gets destroyed - viewpager, trying to change content dynamically

I have a viewpager attached to a tabbed layout and its on page changed listener looks like this
private void setPageChangeListener(FragmentStatePagerAdapter pagerAdapter) {
mViewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener () {
#Override
public void onPageSelected(int position) {
FragmentLifecycleHelper fragment = (FragmentLifecycleHelper) pagerAdapter.getItem(position);
if (fragment != null) {
fragment.onResumeFragment();
}
}
});
}
Now in my child fragments i create the views like this
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mFragmentView = inflater.inflate(R.layout.drawer_account, container, false);
Log.d(TAG, "OnCreateView()");
printLog();
return mFragmentView;
}
which saves a reference to the view (i assume getView() does the same).
Whatsoever, now the called interface method is supposed to change the icon and the fab onClick listener of the hosting activity, the problem is whenever onResume is called the view reference is null.
This is my onResumeFragment()
/********************* INTERFACE METHODS ******************************/
#Override
public void onResumeFragment() {
Log.d(TAG,"onResumeFragment()");
printLog();
if(mFragmentView != null){
((MainActivity) getActivity()).setFabIcon(MainActivity.FAB_ICON.PLUS);
((MainActivity) getActivity()).setFabClickListener(v -> {
// TODO
});
}
}
printLog() just prints the current status of the view reference.
Whenever i log this onCreateView of Fragment 1 gets called when i am in fragment 0 or 3 (so neighbour fragments) onResume then gets called when i actually switch to the correct view. But then getView() and my own reference both return null and i get a NPE if i try to access the hosting activity

Fragment in viewpager savedinstancestate is always null

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.

Android Fragment from Layout - Retain variable data

I've been searching for hours and tried numerous methods but cannot seem to grasp my head around the idea / figure out how to retain/restore data in a ViewPager Fragment when it is destroyed and then recreated.
Here is what I have -
An activity where I setup the ViewPager and PageAdapter
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.view_pager);
//Setup pager and adapter
mPager = (ViewPager) findViewById(R.id.viewpager);
mPagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager());
mPager.setAdapter(mPagerAdapter);
}
My PageAdapter where I setup a fragment with a bundle using .newInstance()
#Override
public Fragment getItem(int position) {
ScreenSlidePageFragment fragment = new ScreenSlidePageFragment();
return fragment.newInstance(position);
}
My Fragment that has a layout that includes a TextView that shows the user a question, a picture, and two True/False buttons. New instance is returned back to the Adapter.
public static ScreenSlidePageFragment newInstance(int position) {
ScreenSlidePageFragment fragment = new ScreenSlidePageFragment();
Bundle args = new Bundle();
args.putInt("page_position", position + 1);
fragment.setArguments(args);
return fragment;
}
//the fragment is newly created for the first time or recreated when exiting the view
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_main, parent, false);
//Handle a question being displayed on each fragment
count = getArguments().getInt("page_position") - 1;
mQuestionText = (TextView)v.findViewById(R.id.questionText);
mQuestionText.setText(bank.get(count).getQuestion());
//change the image depending on correct / incorrect answer
mPhoto = (ImageView)v.findViewById(R.id.imageView);
trueButton = (Button)v.findViewById(R.id.true_button);
falseButton = (Button)v.findViewById(R.id.false_button);
//True Button is pressed
trueButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
if(bank.get(count).getAnswer()) {
mPhoto.setImageResource(R.drawable.right);
clickable = false;
}
else {
mPhoto.setImageResource(R.drawable.wrong);
clickable = false;
}
trueButton.setClickable(clickable);
falseButton.setClickable(clickable);
}
});
What I cannot figure out for the life of me, is how to retain/save that fact that the user has pressed a button and which picture to display when the fragment is restored. I have tried a number of options using onResume(), getArguments(), onSaveInstanceState(), onActivityCreated() etc but none of them seem to work.
I can fix the problem by keeping all my ViewPager pages alive using setOffscreenPageLimit(total pages) but have read this is a bad idea since it takes up a large amount of memory.
Here is an example that I use that can help you where an ArrayList of urls for pictures is saved for later when the view is recreated, you need to override onSavedInstanceState and save your variables in a bundle, then retrieve the values from the bundle when the view is created again, hope this can help
ArrayList<String> picutersUrl = new ArrayList<String>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState!=null)
{
picutersUrl = savedInstanceState.getStringArrayList("My Pictures");
}
#Override
protected void onSaveInstanceState(Bundle bundle)
{
super.onSaveInstanceState(bundle);
bundle.putStringArrayList("My Pictures", picutersUrl );
}
I too have used PagerAdapter to support few static tab widgets. I think you need to add code to ScreenSlidePagerAdapter. Google webpage at PagerAdapter.
Note and read the details on override method instantiateItem. This is where you populate the UI.
Code example for ScreenSlidePagerAdapterfrom (PagerAdapter subclass), using your posted code:
#Override
public Object instantiateItem(ViewGroup container, int position) {
View v = inflater.inflate(R.layout.fragment_main, parent, false);
...
trueButton = (Button)v.findViewById(R.id.true_button);
}
In reality with Views in this framework, you're responsible on saving the state of UI elements. In my ListView, I am caching and saving up to 100 rows of data and refreshes it in an Adapter, for example. For caching, I created custom class containing the UI data.

ViewPager call setUserVisibleHint after first Fragment is loaded

In my application, I have a ViewPager which holds many swipeable Tabs with Fragments inside. I use the setUserVisibleHint method to detect when a Fragment comes to the screen. This works great when the user swipes between tabs but it does not work on the first load. To run the code in the method I have to swipe to left and then back to the first Fragment because the setUserVisibleHint method is called before the onCreateView method.
Do you have any ideas how I can run this code after the first Fragment is visible? Is there a method in the ViewPager or something else?
You can't (and shouldn't) rely on setUserVisibleHint for this. Instead, you should be using a ViewPager.OnPageChangeListener to get callbacks for when a page becomes visible. E.g.
viewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
// do your work
}
});
Note: You can use ViewPager.SimpleOnPageChangeListener if you don't need to listen for all callbacks.
Update
setOnPageChangeListener method is now deprecated, use addOnPageChangeListener instead
viewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
// do your work
}
});
BELOW WORKED FOR ME
Please create a global view like this
View view;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
//inflate view layout
view =inflater.inflate(R.layout.your_fragment, container, false);
// return view
return view;
}
and use this
#Override
public void setUserVisibleHint(boolean isUserVisible)
{
super.setUserVisibleHint(isUserVisible);
// when fragment visible to user and view is not null then enter here.
if (isUserVisible && view != null)
{
onResume();
}
}
and In OnResume method
#Override
public void onResume() {
super.onResume();
if (!getUserVisibleHint()) {
return;
}
//do your stuff here
}
You have to call manually mViewPagerListener.onPageSelected(0); in onCreateView in your root activity/fragment.
I dont understand what you need exactly but you can detect the page of the current fragment by calling ViewPager.getCurrentItem that return integer.
Hope it helps.
Good luck.
Well, its workaround more, and maybe its better to setup OnPageChangeListener, but it works for me. The task is to determine whether the current fragment is visible at creation. You can use the menu to do this. In your fragment:
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true); // set current fragment has its own options menu
}
and this
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
...
if(isMenuVisible()) { // menu should be visible if current fragment is visible right now
setUserVisibleHint(true); // manually set value to true
}
...
return view;
Maybe someone will find it usefull. This works for me.
I check your problem,but I find out the onCreateView method is called before the
setUserVisibleHint method
enter code here
because the setUserVisibleHint method is called before the onCreateView method

Fragment savedInstanceState is ALWAYS null

I have statically created a fragment (via XML). I'm trying to store the last displayed value in a bundle and display it whenever the app is started next. However I am not able to get it to work. For some reason savedInstanceState is always null.
public class DistanceSetterFragment extends Fragment implements SharedPreferences.OnSharedPreferenceChangeListener {
Distance distance = new Distance();
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
if (savedInstanceState!=null )
{
Log.d(this.getClass().getName(),"onCreate savedInstanceState is NOT null");
}
else
{
Log.d(this.getClass().getName(),"onCreate savedInstanceState is null");
}
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
Log.d(this.getClass().getName(),"Distance "+distance);
if (savedInstanceState!=null )
{
Log.d(this.getClass().getName(),"onCreateView savedInstanceState is NOT null");
}
else
{
Log.d(this.getClass().getName(),"onCreateView savedInstanceState is null");
}
return inflater.inflate(R.layout.fragment_distancesetter, container, false);
}
#Override
public void onSaveInstanceState(Bundle outState)
{
super.onSaveInstanceState(outState);
if (distance!=null) {
Log.d(this.getClass().getName(),"Saving DISTANCE_BEAN "+distance);
outState.putSerializable(Constants.DISTANCE_BEAN, distance);
}
else
{
Log.d(this.getClass().getName(),"Distance BEAN IS NULL");
}
outState.putString("", "");
}
}
Below is the fragment XML declared in my main activity XML
<fragment
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="#+id/fragment_distancesetter"
android:layout_below="#id/img_logo_main"
android:name="com.webconfs.xyz.fragments.DistanceSetterFragment"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
/>
As you can see
- I have NOT set setRetainInstance(true) in my Fragment class and
- My fragment XML has an ID associated with it
android:id="#+id/fragment_distancesetter
I had the same problem just now and it was driving me crazy, but as it turns out the fragment was simply being re-added in the activity with every rotation. You didn't add your Activity's code, but you might want to check this is not the case as it's easy to overlook and would explain your problem.
When you put a fragment into an activity statically, the fragment manager will always create a new fragment and attach it to activity. The restoreInstanceState() method will never be called. If you want to do it, you can save state in your activity's restore method, and put save state of your activity to your fragment. Or create fragment dynamically.

Categories

Resources