Search code examples
androidkotlinandroid-jetpack-composeandroid-jetpack-compose-text

How to auto-scroll to the bottom of a Scrolling Log-Type Text composable


I'm trying to implement a Text view for log-type messages where every change to the text would auto-scroll the Text to the last/newly-added line of text. For example if my text size only fits 4 lines:

log 1
log 2
log 3
log 4

and a new line is added, then the output should be:

log 2
log 3
log 4
log 5

The specific problem I'd like to find a solution for is how to scroll down to the bottom every time there's a change in the text value. I tried the accepted solution for TextField, where a LaunchedEffect is triggered during composition phase, but it didn't work in my case:

@Composable
fun LogText(log: State<String>) {

  var logState = rememberScrollState(0)
  val coroutineScope = rememberCoroutineScope()

  LaunchedEffect(coroutineScope) {
      logState.scrollTo(logState.maxValue)
  }

  Text(
      text = log.value,
      modifier = Modifier.verticalScroll(logState),
  )
}

The LaunchedEffect did not change the Text to scroll down to the newest line.


Solution

  • Your usage of LaunchedEffect is wrong. The lambda you are passing to the LaunchedEffect is executed every time the LaunchedEffect's key(s) are changed. You are using remembered coroutine scope as a key, which never changes, therefore your effect is only executed once.

    Also, the LaunchedEffect lambda is launched in coroutine scope by default, so you don't need your own.

    Try it like this:

    @Composable
    fun LogText(log: State<String>) {
    
      var logState = rememberScrollState(0)
    
      LaunchedEffect(log.value) {
          logState.scrollTo(logState.maxValue)
      }
    
      ...
    }
    

    this means: everytime the log.value changes, execute the lambda.

    And one more thing: if your text gets really long, drawing it like that will be quite heavy. I would consider splitting it by lines to List<String> and using LazyColumn with multiple Text items...