Home > Back-end >  How to scroll a Column to a new position whenever a ViewModel emits a new value?
How to scroll a Column to a new position whenever a ViewModel emits a new value?

Time:11-08

I have a case where I have a ViewModel that emits scroll values to a StateFlow. Now, my Composable view should scroll to the most recent value, but I can't figure out how to achieve that.

My view model looks something like this:

class MyViewModel : ViewModel() {
    private val scrollFlow = MutableStateFlow<Int>(0)

    fun getScrollFlow(): StateFlow<Int> = scrollFlow.asStateFlow()
}

And my view is like this:

@Composable
fun MyScrollingView() {
    val viewModel = viewModel()

    val scroll by viewModel.getScrollFlow().collectAsState()
    val scrollState = rememberScrollState(0)

    Column(modifier = Modifier.verticalScroll(scrollState)) {
        // Content here
    }
}

The thing here is, how do I make the scroll state to react to the values coming from the view model?

CodePudding user response:

You can try doing it inside a LaunchedEffect and use the scrollFlow value as its key, every time the scrollFlow emits new value, the LaunchedEffect will trigger its block.

@Composable
fun MyScrollingView() {
    val viewModel = viewModel()

    val scroll by viewModel.getScrollFlow().collectAsState()
    val scrollState = rememberScrollState(0)

    Column(modifier = Modifier.verticalScroll(scrollState)) {
        // Content here
    }

    LaunchedEffect(scroll) {
        scrollState.animateScrollTo(0)
        //or
//        scrollState.scrollTo(0)
    }
}

I'm just not sure if this would work in your case especially having this statement inside your composable though.

val viewModel = viewModel()

CodePudding user response:

There are many way to achieve that, for example:

val scrollState = rememberScrollState(0)

rememberCoroutineScope().launch {
    viewModel.getScrollFlow()
    .flowOn(Dispatchers.Default)
    .onEach{scrollVal ->
        scrollState.animateScrollTo(scrollVal)
    }.collect()
}

Actually I'm afraid that code could be launched on every recomposition and is not ideal so maybe:

remember{
    viewModel
    .getScrollFlow()
    .onEach{scrollVal ->
        scrollState.animateScrollTo(scrollVal)
    }
}.collectAsState(0, Dispatchers.Default)

or

val scroll by viewModel.getScrollFlow().collectAsState(0, Dispatchers.Default)
LaunchedEffect(scroll){
    crollState.animateScrollTo(scroll)
}
  • Related