Android ViewPager setCurrentItem not working after onResume
Asked Answered
C

19

47

I've got this strange issue, ViewPager's setCurrentItem(position, false) works perfectly fine, then im switching to another activity, and after I'm back to the first activity, the ViewPager always ends up on the first item. Even though I've added setCurrentItem to onResume method it still ignores it. It's not even throwing any exception when I'm trying to set item to out of bounds index. Though later on when I call this method, when the button "next" is tapped, it works like expected. Checked my code 10 times for any possible calls to setCurrentItem(0) or something but it's just not there at all.

Clarettaclarette answered 11/10, 2013 at 11:12 Comment(2)
any snippet would be betterPerambulator
I had a similar problem, I just moved code that sets the current item, below setting the adapter. I mean make sure you first set the adapter then set the position.Thymelaeaceous
S
95

i can't really answer WHY exactly this happens, but if you delay the setCurrentItem call for a few milliseconds it should work. My guess is that because during onResume there hasn't been a rendering pass yet, and the ViewPager needs one or something like that.

private ViewPager viewPager;

@Override
public void onResume() {
    final int pos = 3;
    viewPager.postDelayed(new Runnable() {

        @Override
        public void run() {
            viewPager.setCurrentItem(pos);
        }
    }, 100);
}

UPDATE: story time

so today i had the problem that the viewpager ignored my setCurrentItem action, and i searched stackoverflow for a solution. i found someone with the same problem and a fix; i implemented the fix and it didn't work. whoa! back to stackoverflow to downvote that faux-fix-provider, and ...

it was me. i implemented my own faulty non-fix, which i came up with the first time i stumbled over the problem (and which was later forgotten). i'll now have to downvote myself for providing bad information.


the reason my initial "fix" worked was not because of of a "rendering pass"; the problem was that the pager's content was controlled by a spinner. both the spinners and the pagers state were restored onResume, and because of this the spinners onItemSelected listener was called during the next event propagation cycle, which did repopulate the viewpager - this time using a different default value.
removing and resetting the listener during the initial state restoration fixed the issue.

the fix above kind-of worked the first time, because it set the pagers current position after the onItemSelected event fired. later, it ceased to work for some reason (probably the app became too slow - in my implementation i didn't use 100ms, but 10ms). i then removed the postDelayed in a cleanup cycle, because it didn't change the already faulty behaviour.

update 2: i can't downvote my own post. i assume, honorable seppuku is the only option left.

Schweitzer answered 8/11, 2013 at 12:14 Comment(15)
What do you mean the pager's content was controlled by a spinner? Are you talking about the ViewPager's underlying implementation, or saying the content of each page in the viewpager contained a spinner? And I'm still having this bug even if the first thing I do in onResume() is call super.onResume(). By doing that, view states should be restored, and I shouldn't have to deal with what you described aboveBecalm
no, i mean: depending on the selected item in the spinner, the viewpager (i.e. the adapter) was populated with different items. in onResume both the spinner and the pager were re-initialized and the spinner's onItemSelected callback for updating the viewpager "overwrote" the correctly set viewpager position. it was my own fault, the viewpager worked correctly.Schweitzer
I see. I had the same issue but quite a different root cause. My ViewPager was hosted by a fragment and I was using getFragmentManager() instead of getChildFragmentManager() when passing a FragmentManager to my FragmetStatePagerAdapterBecalm
Applying rendering delay. Felt awkward when read your answer but it did solve my problem...Upright
@Crawler: it's a trap! don't do it. it might seem to work at first, and gets unpredictable results a couple of months later when the code had time to stew a bit; then it turns into debugging hell and unpredictable 1-in-a-100 odd-behaviours that leads to testers writing bug reports like "X fails sometimes for no reason".Schweitzer
@Schweitzer thanks for the warning.....But I now again stuck in problem.... Do you have any lead in solvin this issue?Upright
as i said, for me the problem was something entirely different, i.e. calling setCurrentItem() twice. i guess the same thing applies in your case too.Schweitzer
@Schweitzer that solvs the problem but it is not bringing good experience for the user at all with animation. There must be some better way to do that!Lili
@Schweitzer 2:31am in the morning and finally your answer solved my problem by post delaying before setting the viewpager item position as well.Photokinesis
@Photokinesis i'm sure you're either calling setCurrentItem again afterwards or assign a new adapter, thanks to some lifecycle shenanigans or maybe after some async operation. DO NOT depend on postDelayed, it's a hack and practically technical debt. your problem lies elsewhere!Schweitzer
@Schweitzer yes correct Calling setCurrentItem() twice doesn't work..good catchBoudicca
This is happend in ViewPager2 also;;;;;;;;;;;; wtfSommers
This isn't working for me, and neither are any of the other solutions mentioned on this thread. I'm stumped, what a strange problem.Anthelion
Thank you @Schweitzer you are saved my day, this really worked for me when I was switching fragments and trying to set the position in onCreate. I guess it needs time to set all fragments etcCheney
wow, that story deserves a netflix adaptationInglorious
D
34

I had a similar issue in the OnCreate of my Activity. The adapter was set up with the correct count and I applied setCurrentItem after setting the adapter to the ViewPager however is would return index out of bounds. I think the ViewPager had not loaded all my Fragments at the point i set the current item. By posting a runnable on the ViewPager i was able to work around this. Here is an example with a little bit of context.

    // Locate the viewpager in activity_main.xml
    final ViewPager viewPager = (ViewPager) findViewById(R.id.pager);

    // Set the ViewPagerAdapter into ViewPager
    viewPager.setAdapter(new ViewPagerAdapter(getSupportFragmentManager()));

    viewPager.setOffscreenPageLimit(2);

    viewPager.post(new Runnable() {
        @Override
        public void run() {
            viewPager.setCurrentItem(ViewPagerAdapter.CENTER_PAGE);
        }
    });
Derris answered 8/4, 2015 at 15:45 Comment(2)
@TylerPfaff this just worked for me too. Apparently, the ViewPager ignores "early-into-the-activity" instructions because it's not yet aware of what to renderFritter
@TylerPfaff I believe it has something to do with when the ViewPager and its children are measured. It appears to ignore setCurrentItem until the has completed. The runnable posted on the ViewPager is guaranteed to execute after that measure.Derris
S
19

ViewTreeObserver can be used to avoid a static delay.

Kotlin:

Feel free to use Kotlin extension as a concise option.

view_pager.doOnPreDraw {
    view_pager.currentItem = 1
}

Please, make sure you have a gradle dependency: implementation 'androidx.core:core-ktx:1.3.2' or above

Java

OneShotPreDrawListener.add(view_pager, () -> view_pager.currentItem = 1);
Syncom answered 4/11, 2020 at 3:0 Comment(1)
great answer - only solution that worked in viewpager2!Cubeb
N
17

I found a very simple workaround for this:

    if (mViewPager.getAdapter() != null)
        mViewPager.setAdapter(null);
    mViewPager.setAdapter(mPagerAdapter);
    mViewPager.setCurrentItem(desiredPos);

And, if that doesn't work, you can put it in a handler, but there's no need for a timed delay:

        new Handler().post(new Runnable() {
            @Override
            public void run() {
                mViewPager.setCurrentItem(desiredPos);
            }
        });
Nutrient answered 29/4, 2014 at 11:11 Comment(1)
Thank you, second approach fixed my problem.Triptych
W
6

A modern approach in a Fragment or Activity is to call ViewPager.setcurrentItem(Int) function in a coroutine in the context of Dispatchers.Main :

lifecycleScope.launch(Dispatchers.Main) {
   val index = 1
   viewPager.setCurrentItem(index)
}
Weiweibel answered 9/3, 2020 at 14:45 Comment(1)
does not work for me. Its appear that the fragment is not ready weat when set curretn item was called java.lang.IllegalArgumentException: No view found for id 0x7f09021dNickolasnickolaus
P
5

I had similar bug in the code, the problem was that I was setting the position before changing the data.

The solution was simply to set the position afterwards and notify the data changed

notifyDataSetChanged()
setCurrentItem()
Pilgrim answered 18/12, 2017 at 13:42 Comment(1)
ViewPager2 call setCurrentItem(1, true) after notifyDataSetChanged() not working!Sheltonshelty
L
4

I have the same problem and I edit

@Override
public int getCount() { return NUM_PAGES; }

I set NUM_PAGES be mistake to 1 only.

Lidstone answered 19/3, 2015 at 15:10 Comment(1)
That was my problem too... ty!Higgler
S
4

I've used the post() method described here and sure enough it was working great under some scenarios but because my data comes from the server, it was not the holy grail.

My problem was that i want to have

notifyDataSetChanged

called at an arbitrary time and then switch tabs on my viewPager. So right after the notify call i have this

ViewUtilities.waitForLayout(myViewPager, new Runnable() {
    @Override
    public void run() {
        myViewPager.setCurrentItem(tabIndex , false);
    }
});

and

public final class ViewUtilities {
    public static void waitForLayout(final View view, final Runnable runnable) {
        view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            //noinspection deprecation
                view.getViewTreeObserver().removeGlobalOnLayoutListener(this);

                runnable.run();
            }
        });
    }
}

Fun fact: the //noinspection deprecation at the end is because there is a spelling mistake in the API that was fixed after API 16, so that should read

removeOnGlobalLayoutListener
       ^^
      ON Global

instead of

removeGlobalOnLayoutListener
            ^^
            ON Layout

This seems to be covering all cases for me.

Seaplane answered 21/11, 2016 at 17:9 Comment(0)
S
4

some guy wrote on forums here. https://code.i-harness.com/en/q/126bff9 worked for me

 if (mViewPager.getAdapter() != null)
    mViewPager.setAdapter(null);
mViewPager.setAdapter(mPagerAdapter);
mViewPager.setCurrentItem(desiredPos);
Sapwood answered 8/1, 2018 at 20:8 Comment(1)
You are the best. This worked at last. i was getting data from a volley json request. the views were initially empty. I tried a lot of solutions but none of them worked. but your worked like magic. thanks.Hoffman
S
4

Solution (in Kotlin with ViewModel etc.) for those trying to set the current item in the onCreate of Activity without the hacky Runnable "solutions":

class MyActivity : AppCompatActivity() {
    lateinit var mAdapter: MyAdapter
    lateinit var mPager: ViewPager
    // ...

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.fragment_pager)
        // ...
        mainViewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
        mAdapter = MyAdapter(supportFragmentManager)
        mPager = findViewById(R.id.pager)

        mainViewModel.someData.observe(this, Observer { items ->
            items?.let {
                // first give the data to the adapter
                // this is where the notifyDataSetChanged() happens
                mAdapter.setItems(it) 
                mPager.adapter = mAdapter // assign adapter to pager
                mPager.currentItem = idx // finally set the current page
            }
        })

This will obviously do the correct order of operations without any hacks with Runnable or delays.

For the completeness, you usually implement the setItems() of the adapter (in this case FragmentStatePagerAdapter) like this:

internal fun setItems(items: List<Item>) {
    this.items = items
    notifyDataSetChanged()
}
Sanorasans answered 22/3, 2019 at 20:27 Comment(0)
R
2

This is a lifecycle issue, as pointed out by several posters here. However, I find the solutions with posting a Runnable to be unpredictable and probably error prone. It seems like a way to ignore the problem by posting it into the future.

I am not saying that this is the best solution, but it definitely works without using Runnable. I keep a separate integer inside the Fragment that has the ViewPager. This integer will hold the page we want to set as the current page when onResume is called next. The integer's value can be set at any point and can thus be set before a FragmentTransaction or when resuming an activity. Also note that all the members are set up in onResume(), not in onCreateView().

public class MyFragment extends Fragment
{
    private ViewPager           mViewPager;
    private MyPagerAdapter      mAdapter;
    private TabLayout           mTabLayout;
    private int                 mCurrentItem = 0; // Used to keep the page we want to set in onResume().

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    {
        View view = inflater.inflate(R.layout.my_layout, container, false);
        mViewPager = (ViewPager) view.findViewById(R.id.my_viewpager);
        mTabLayout = (TabLayout) view.findViewById(R.id.my_tablayout);
        return view;
    }

    @Override
    public void onResume()
    {
        super.onResume();

        MyActivity myActivity = (MyActivity) getActivity();
        myActivity.getSupportActionBar().setTitle(getString(R.string.my_title));

        mAdapter = new MyPagerAdapter(getChildFragmentManager(), myActivity);
        mViewPager.setAdapter(mAdapter);
        mViewPager.setOffscreenPageLimit(PagerConstants.OFFSCREEN_PAGE_LIMIT);
        mViewPager.setCurrentItem(mCurrentItem); // <-- Note the use of mCurrentItem here!
        mTabLayout.setupWithViewPager(mViewPager);
    }

    /**
     * Call this at any point before needed, for example before performing a FragmentTransaction.
     */    
    public void setCurrentItem(int currentItem)
    {
        mCurrentItem = currentItem;

        // This should be called in cases where onResume() is not called later,
        // for example if you only want to change the page in the ViewPager
        // when clicking a Button or whatever. Just omit if not needed.
        mViewPager.setCurrentItem(mCurrentItem); 
    }


}
Ragi answered 26/8, 2015 at 19:54 Comment(1)
In my case, I moved all the init lines (like setting adapter, listeners, etc.) from onResume() to onCreateView() and it workedAintab
M
2

For me this worked setting current item after setting adapter

viewPager.setAdapter(new MyPagerAdapter(getSupportFragmentManager()));

viewPager.setCurrentItem(idx);

pagerSlidingTabStrip.setViewPager(viewPager);// assign viewpager to tabs
Mayhap answered 28/6, 2016 at 6:23 Comment(0)
P
2

I was working on this problem for one week and I realized that this problem happens because I was using home activity context in view pager fragments and we can only use context in fragment after it gets attached to activity..
When a view pager gets created, activity only attach to the first (0) and second (1) page. When you open the second page, the third page gets attached and so on! When you use setCurrentItem() method and the argument is greater than 1, it wants to open that page before it is attached, so the context in fragment of that page will be null and the application gets crashed! That's why when you delay setCurrentItem(), it works! At first it gets attached and then it'll open the page...

Photosphere answered 28/8, 2019 at 13:39 Comment(0)
C
1

I've done it this way to restore the current item:

@Override
protected void onSaveInstanceState(Bundle outState) {

    if (mViewPager != null) {
        outState.putInt(STATE_PAGE_NO, mViewPager.getCurrentItem());
    }

    super.onSaveInstanceState(outState);
}

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {

    if (savedInstanceState != null) {
        mCurrentPage = savedInstanceState.getInt(STATE_PAGE_NO, 0);
    }

    super.onRestoreInstanceState(savedInstanceState);
}

@Override
protected void onRestart() {
    mViewPager.setCurrentItem(mCurrentPage);
         super.onRestart();
}
Companionship answered 11/10, 2013 at 11:20 Comment(2)
The thing is im not restoring instance state, its just onStart and onResume method called. Ive got the position stored properly, aswell i looked into my Viewpager object at debug mode and it has mCurItem set properly, yet its still showing the first elementClarettaclarette
I can't remember exactly but I think I has a similar problem and I used the code above. But maybe not exactly the same.Companionship
C
1

By the time I call setCurrentItem() the view is about to be recreated. So in fact I invoke setCurrentItem() for the viewpager and afterwards the system calls onCreateView() and hence creates a new viewpager.

This is the reason for me why I do not see any changes. And this is the reason why a postDelayed() may help.

Theoretical solution: Postpone the setCurrentItem() invocation until the view has been recreated.

Practical solution: I have no clue for a stable and simple solution. We should be able to check if the class is about to recreate it's view and if that is the case postpone the invocation of setCurrentItem() to the end of onCreateView()

Cur answered 8/10, 2016 at 20:4 Comment(0)
M
1

I use the dsalaj code as a reference. If necessary I share the code with the complete solution.

I also strongly recommend using ViewPager2

Solution

Both cases have to go within the Observer {}:

  1. First case: Initialize the adapter only when we have the first data set and not before, since this would generate inconsistencies in the paging. To the first data set we have to pass it as the argument of the Adapter.

  2. Second case: From the first change in the observable we would have from the second data sets onwards which have to be passed to the Adapter through a public method only if we have already initialized the adapter with a first data set.

GL

Midis answered 8/4, 2020 at 17:14 Comment(2)
This continue happening on viewPager2 . it sucksCheckerbloom
I remember very well that this had worked for me brother. Try to debug the critical points. Then it evaluates how the adapter is initialized if the observable has data, and how the adapter sets the new data when the observable is updatedMidis
L
1

I was confused with the onActivityCreated() getting invoked for unrelated tab @Mahdi Arabpour was an eye opener for me :)

For me the problem was the third page (as elaborated by @Mahdi Arabpour above) was getting reconstructed when I click the second tab, etc and it was losing its data adapter, setting it again in onActivityCreted solves my problems:

    if (myXXRecyclerAdapter != null) {
        myXXRecyclerAdapter = new MyXXRecyclerAdapter(myStoredData);
        mRecyclerView.setAdapter(myXXRecyclerAdapter );
        return;
    }
Ligniform answered 21/11, 2020 at 7:28 Comment(0)
T
1

The only solution works for me with ViewPager2 is:

mViewPager.setCurrentItem(index, false);

Where smoothScroll = false is a MUST!

If I try to use call without specifying smoothScroll parameter:

mViewPager.setCurrentItem(index);

It was not working

Tall answered 11/10, 2023 at 10:30 Comment(0)
C
-1

You need to call pager.setCurrentItem(activePage) right after pager.setAdapter(buildAdapter())

@Override
public void onResume() {
    if (pager.getAdapter() != null) {
        activePage=pager.getCurrentItem();
        Log.w(getClass().getSimpleName(), "pager.getAdapter()!=null");
        pager.setAdapter(null);

    }

    pager.setAdapter(buildAdapter());            
    pager.setCurrentItem(activePage);
}
Cowardice answered 7/3, 2015 at 23:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.