I have a screen with a viewPager from accompanist. One thing that I'm having trouble with is syncin...
c
I have a screen with a viewPager from accompanist. One thing that I'm having trouble with is syncing my viewModel.currentPage and rememberPagerState.currentPage, and remembered that one of the points of compose is single source of truth.
Copy code
@Composable
fun SignUpScreen(
    viewModel: SignUpViewModel = hiltViewModel()
) {
    val pagerState = rememberPagerState(5)
In order to have a single source of truth, should I just move
val pagerState = rememberPagerState(5)
into my ViewModel?
2
s
I had exactly the same question a few days back 😄 https://kotlinlang.slack.com/archives/CJLTWPH7S/p1627737770148200
But when I move PagerState to my ViewModel State class I can't call pagestate.animate* functions from ViewModel. It has to be called from the Composable function.
a
I was working on a prototype of this today, to bridge
SavedStateHandle
with the Compose
Saver
APIs: https://gist.github.com/alexvanyo/0406250c67e02fae36dd5360e915bed2 Usage looks like
Copy code
val pagerState: PagerState = savedStateHandle.saveable(
        key = "pagerState",
        saver = PagerState.Saver
    ) {
        PagerState(
            pageCount = 10,
            offscreenLimit = 2,
        )
    }
which sort of mimics
rememberSaveable
👍 1
c
Interesting. I may copy what Alex has above, but it does seem like something like this should be provided out of the box since a big part of compose is single source of truth. Chris Banes, curious to hear your thoughts on it.
s
Thanks @Alex Vanyo but calling animate to next page function on PagerState should be called from Composable LaunchEffect, still bridging might be very useful in other cases.
d
IIRC I have solved this by using snapshotFlow and sending events back to my presenter (view model for you) in it's flow's collect function. this is also mentioned in accompanyist's pager docs somewhere.
c
Oooh really? I will look that up
c
Alex’s code looks good to me. Whether you use a
snapshotFlow
or hoist the state to the VM mostly depends on what is the source of truth. Does the VM need to know about every change of the Pager? If so, hoisting makes sense. If it just needs to know about a page change event, hoisting might be a bit over the top. As with a lot of things in software dev, it depends 😬
Note however,
SavedStateHandle
is an Android thing. If you ever want to go KMP then you need to rely on saveable (which
rememberPagerState()
already does)
c
I just need to know the current page. Right now I am essentially trying to sync them up which is doable because I disable swiping and I only move forward/back based on button presses. I am looking to go KMP one day, so yes I would like an approach that sets me up for that.
s
@cb is there way to move to next page from Viewmodel itself? i have PageState combined with my ScreenState class, i have to performe some task before moving to next page, but i am getting error when i call animateScrollToPage form viewModel.
2
a
What error are you seeing?
animateScrollToPage
should be a
suspend fun
which you could call from any suspending context from your
ViewModel
if your
pagerState
is hoisted there. If your
ViewModel
exposes a
suspend fun
, the animation could still be tied to a scope driven by a specific composable (
rememberCoroutineScope()
or
LaunchedEffect
), otherwise it could be a scope that outlives composition (like
viewModelScope
)
☝️ 1
c
I tried putting
val pagerState = rememberPagerState(5)
into my VM, but it didn't compile. I don't think I can put rememberPagerState into the VM that way? But again, I'm very new to this. maybe I'm missing something basic Alex.
a
Right!
rememberPagerState
is annotated with
@Composable
, so you can only call that from a
@Composable
function. It needs to be, since its implementation calls
@Composable rememberSaveable
. AAC `ViewModel`s outlive composition, so you can’t call any
@Composable
functions from them. However,
PagerState
itself is a state holder, which doesn’t have to exist within a
@Composable
, and can be hoisted out of it completely. So you could do
val pagerState = PagerState(5)
in your
ViewModel
instead. The downside there is that the pager state won’t be saved through process death, which
rememberPagerState
does protect you against (via
rememberSaveable
) To fix that, you could either listen for changes to save to
SavedStateHandle
(with
snapshotFlow
) or the gist I shared earlier
c
Ah. Okay. Thanks for tying that together (and explaining so well) Alex. This makes way more sense now. Cheers!
s
@Alex Vanyo I'm getting this error
java.lang.IllegalStateException: A MonotonicFrameClock is not available in this CoroutineContext. Callers should supply an appropriate MonotonicFrameClock using withContext.
But I think it is due to
animateScrollToPage
called from ViewModelScope.
d
I second the usecase where I want VM/Presenter to controll the page change. I currently do it with watching with snapshot flow + set state + launched effect, but there's some usecase where this l leads to glitches in the TabRow indicator. I'll still have to investigate and perhaps will post a bug.
1
193 Views