ViewPager2 default position

First off, I think that the accepted answer shouldn't be @hosseinAmini 's, since it's suggesting to use a delay to work around the problem. You should first be looking for what the assumed bug is caused by, rather than trusting unreasonable solutions like that.

@Rune's proposal is correct, instead; so I'm quoting their code in my answer:

viewPager.post {
    viewPager.setCurrentItem(1, true)
}

The only thing I'd argue about is the aforementioned one's belief that their solution is just deferring the execution of that lambda in the next run cycle. This wouldn't make anything buggy work properly. Rather, what it is actually being done is deferring the execution of that lambda to once the view has been attached to a window, which implies it's also been added to a parent view. Indeed, there looks to be an issue as to changing the current ViewPager2 item before being attached to a window. Some evidence to support this claim follows:

  • Using whichever Handler won't work nearly as effectively.

    Handler(Looper.getMainLooper()).post {
        viewPager.setCurrentItem(1, true) // Not working properly
    }
    

    From a theoretical standpoint, it might incidentally work due to the ViewPager2 being attached to a window acquiring priority in the message queue of the main looper, but this shouldn't ever be relied upon as there's just no guarantee that it'll work (it's even more likely it won't) and if it even turned out to be working, further investigation running multiple tests should make my point clear.

  • View.handler gets null, which means the view hasn't been attached to any window yet.

    View.handler // = null
    

    Despite Android UI being tied to the main looper, which will always uniquely correspond to the main thread –hence also called the UI thread,– a weird design choice stands in the handler not being associated to each view until they get attached to a window. A reason why this may lay on the consequent inability of views to schedule any work on the main thread while they're not part of the hierarchy, which may turn useful when implementing a view controller that schedules view updates while unaware of their lifecycle (in which case it would employ the View's handler, if any — or just skip scheduling whatever it was going to if none).

EDIT:

Also, @josias has pointed out in a comment that it'd be clearer to use:

viewPager.doOnAttach {
    viewPager.setCurrentItem(1, true)
}

Thanks for that suggestion! It expresses better the actual intent, rather than relying on the behavior of the View.post method.


I think an easier more reliable fix is to defer to next run cycle instead of unsecure delay e.g

viewPager.post {
  viewPager.setCurrentItem(1, true)
}

setCurrentItem(int item, boolean smoothScroll) works correctly in ViewPager but in ViewPager2 it does not work as expected. Finally, I faced this problem by adding setCurrentItem(int item, boolean smoothScroll) method into a delay like this:

Handler().postDelayed({
     view.viewPager.setCurrentItem(startPosition, false)
}, 100)

Do not use timers, you will run into a lot of probable states in which the user has a slow phone and it actually takes a lot longer than 100 ms to run, also, you wouldn't want too slow of a timer making it ridiculously un-reliable.

Below we do the following, we set a listener to our ViewTreeObserver and wait until a set number of children have been laid out in our ViewPager2's RecyclerView (it's inner working). Once we are sure x number of items have been laid out, we start our no-animation scroll to start at the position.

val recyclerView = (Your ViewPager2).getChildAt(0)

recyclerView.apply {
   val itemCount = adapter?.itemCount ?: 0
   if(itemCount >= #(Position you want to scroll to)) {
      viewTreeObserver.addOnGlobalLayoutListener(object: ViewTreeObserver.OnGlobalLayoutListener {
      override fun onGlobalLayout() {
         viewTreeObserver.removeOnGlobalLayoutListener(this)

         // False for without animation scroll
         (Your ViewPager2).scrollToPosition(#PositionToStartAt, false)
      }
   }
}