20

I am using JetPack Compose Pager from Accompanist and I'm wonder how do I know exactly when my page is Showed at screen. Like onPageSelected method from ViewPager.

Here is my code:

HorizontalPager(
        state = pagerState,
        modifier = Modifier
            .fillMaxSize()
            .background(MaterialTheme.colors.background)
    ) { page ->
         // This method reinvoked many times.

      }

'Cause currently each recomposition would invoke that callback method from Pager.

Sirelon
  • 6,446
  • 5
  • 26
  • 30

6 Answers6

29

I think it will be better to use

LaunchedEffect(pagerState) {
    snapshotFlow { pagerState.currentPage }.collect { page ->
        // do your stuff with selected page
    }
}
Sirelon
  • 6,446
  • 5
  • 26
  • 30
ysfcyln
  • 2,857
  • 6
  • 34
  • 61
  • 5
    To remove the duplicate state at the first composition use the flow with "distinctUntilChanged". snapshotFlow { pagerState.currentPage }.distinctUntilChanged().collect { page -> // do your stuff with selected page } – Omar Abdan Nov 29 '21 at 09:27
  • From the snapshotFlow docs: If the result of block is not equal to the previous result, the flow will emit that new result. (This behavior is similar to that of Flow.distinctUntilChanged.) – geekyvad Jan 11 '23 at 21:16
  • It's worth to mention that this `stateFlow` api available only in `compose.foundation` and only from version 1.4. In accompanist it's marked as deprecated. see [source](https://android-review.googlesource.com/c/platform/frameworks/support/+/2232050/40/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt) – Yazazzello Mar 10 '23 at 14:05
  • How to show list (LazyColumn) when page changed? Because in your above code, we can't use Composable inside that – Kishan Solanki Aug 08 '23 at 14:48
11

To complete @ysfcyln solution here is the official documentation :

https://google.github.io/accompanist/pager/#reacting-to-page-changes

the full example there is :

val pagerState = rememberPagerState()

LaunchedEffect(pagerState) {
    // Collect from the a snapshotFlow reading the currentPage
    snapshotFlow { pagerState.currentPage }.collect { page ->
        AnalyticsService.sendPageSelectedEvent(page)
    }
}

VerticalPager(
    count = 10,
    state = pagerState,
) { page ->
    Text(text = "Page: $page")
}
  • Thanks, your complete example solved my issues. I had added the LaunchedEffect inside the pager's content, which caused the analytics log to trigger more often than needed. Right now it's only triggered once. ... I'd like to add that the event is only triggered when the new page would be active when releasing a drag & hold action on the pager, which is when the selected tabs also visually changes. In other words: drag & hold only a few pixels isn't considered a new screen event, but dragging the new page at least halfway on the screen is. – P Kuijpers Feb 03 '23 at 09:41
4

Here is my solution which worked quite well for me. Extra thing with in it was "distinctUntilChanged"

LaunchedEffect(pagerState) {
    snapshotFlow {
        pagerState.currentPage
    }.distinctUntilChanged().collect { page ->
        // Now you can use selected page
        onPageChangeListener.invoke(page)
    }
}
Ali Nawaz
  • 2,016
  • 20
  • 30
  • 1
    Great addition! I also noticed multiple calls in my implementation. However, the post of Dagnogo solved it for me. Check out my comment why. As far as I can see distinctUntilChanged shouldn't be necessary in that case, although it's always good to have an additional safeguard! – P Kuijpers Feb 03 '23 at 09:29
2

This is a solution simpler than the one from the official documentation

LaunchedEffect(pagerState.currentPage) {
        // do your stuff with pagerState.currentPage
}
mtotschnig
  • 1,238
  • 10
  • 30
1

Use a variable to keep track

val trigger by remember { mutableStateOf (false) }
HorizontalPager(
        state = pagerState,
        modifier = Modifier
            .fillMaxSize()
            .background(MaterialTheme.colors.background)
    ) { page ->
         // Shown successfully,
         trigger = true //use at other places as a callback to change state
      }
Richard Onslow Roper
  • 5,477
  • 2
  • 11
  • 42
1

We can do our tasks without any callbacks using PagerState.

val pagerState = rememberPagerState(pageCount = list.size)
        
        HorizontalPager(state = pagerState) { page ->
            // Our page content
            CardItemView(page, list[page])
        }

// Do your tasks based on the current page selected using pagerState.currentPage
        Toast.makeText(
            LocalContext.current,
            "Page selected ${pagerState.currentPage}",
            Toast.LENGTH_SHORT
        ).show()
vbp
  • 1,157
  • 10
  • 16
  • 1
    That is not what "reacting" is. Bold answer, but completely misses the point. – ino Aug 20 '22 at 13:31