:wave: Hi, I’m trying to make a collapsible FAB a...
# compose
g
👋 Hi, I’m trying to make a collapsible FAB and I’m confused why recomposition isn’t working in one case. 🧵
🧵 4
The same approach works well with
LazyListState
s
It would be appreciated if you can move the code inside the thread, to not fill up space in the main chat screen
g
If I have the following code it works as expected:
Copy code
@Composable
fun ColumnWithFab(
  modifier: Modifier = Modifier,
  scrollState: ScrollState = rememberScrollState(),
  content: @Composable ColumnScope.() -> Unit
) {
  var previousScrollOffset by remember { mutableIntStateOf(scrollState.value) }
  var wasScrollingUp by remember { mutableStateOf(true) }

  val isScrollingUp by remember {
    derivedStateOf {
      when {
        abs(scrollState.value - previousScrollOffset) > SCROLL_OFFSET_THRESHOLD -> scrollState.value < previousScrollOffset
        else -> null
      }?.also {
        previousScrollOffset = scrollState.value
        wasScrollingUp = it
      } ?: wasScrollingUp
    }
  }

  Box(modifier) {
    Column(
      content = content,
      modifier = Modifier
        .verticalScroll(state = scrollState),
    )

    Fab(
      showText = isScrollingUp,
      modifier = Modifier
        .align(Alignment.BottomEnd)
        .padding(CanvasTheme.dimens.normal75),
    )
  }
}

@Composable
fun Fab(
  modifier: Modifier = Modifier,
  showText: Boolean,
) {
  FloatingActionButton(
    onClick = { },
    shape = CircleShape,
    backgroundColor = Color.Cyan,
    modifier = modifier,
  ) {
    Row(verticalAlignment = CenterVertically, modifier = Modifier.padding(horizontal = CanvasTheme.dimens.normal100)) {
      Icon(Icons.Filled.Add, contentDescription = null)
      AnimatedVisibility(showText) {
        Text(text = "Expanded")
      }
    }
  }
}
Every time I scroll the
isScrollingUp
is recalculated and updated correctly, but if I extract the code that does the calculation to a separate method it doesn’t work:
Copy code
@Composable
fun ColumnWithFab(
  modifier: Modifier = Modifier,
  scrollState: ScrollState = rememberScrollState(),
  content: @Composable ColumnScope.() -> Unit
) {
  Box(modifier) {
    Column(
      content = content,
      modifier = Modifier
        .verticalScroll(state = scrollState),
    )

    Fab(
      showText = scrollState.isScrollingUp(),
      modifier = Modifier
        .align(Alignment.BottomEnd)
        .padding(CanvasTheme.dimens.normal75),
    )
  }
}

@Composable
private fun ScrollState.isScrollingUp(): Boolean {
  var wasScrollingUp by remember(this) { mutableStateOf(true) }
  var previousScrollOffset by remember(this) { mutableIntStateOf(value) }

  return remember(this) {
    derivedStateOf {
      when {
        abs(value - previousScrollOffset) > SCROLL_OFFSET_THRESHOLD -> value < previousScrollOffset
        else -> null
      }?.also {
        previousScrollOffset = value
        wasScrollingUp = it
      } ?: wasScrollingUp
    }.value
  }
}

@Composable
fun Fab(
  modifier: Modifier = Modifier,
  showText: Boolean,
) {
  FloatingActionButton(
    onClick = { },
    shape = CircleShape,
    backgroundColor = Color.Cyan,
    modifier = modifier,
  ) {
    Row(verticalAlignment = CenterVertically, modifier = Modifier.padding(horizontal = CanvasTheme.dimens.normal100)) {
      Icon(Icons.Filled.Add, contentDescription = null)
      AnimatedVisibility(showText) {
        Text(text = "Expanded")
      }
    }
  }
}
The is
isScrollingUp()
is called only once. Even though the scroll state is read in the
isScrollingUp()
it doesn’t recompose when the scroll state changes. (edited)