I have these two variables at the top of my Compos...
# compose
n
I have these two variables at the top of my Composable:
var enabled by remember { mutableStateOf(true) }
and
var visible by remember { mutableStateOf(true) }
. When I reload the Composable with new data, these variables do not return to their original
true
value. Why is that? And how can I make sure they are updated too?
k
Why use
remember
then?
n
Because I need to change that value in the Composable. Let me show you some code.
Copy code
val color by animateColorAsState(
        if (visible) tileColor else Color.White,
        animationSpec = tween(durationMillis = 1000)
    )
k
You can still do that with
var
or
mutableStateOf
n
Ok, thanks for the pointer, makes sense! 😊
k
remember
is for maintaining the value over multiple re-entrances into your composable
n
What I don't understand is if I just use
var
, my color does not change.
k
Probably because the relevant code (your
if
statement) can't track those changes, so you do need the
mutableStateOf
wrapper
You need to tell the compiler that this "thing" not only changes, but is also observable (to borrow terminology from the imperative way of doing things) where those changes trigger some logic elsewhere
a
(nit: snapshot observation does not involve the compose compiler, it's purely a runtime construct)
👍 2
It might help guide any advice if you post the whole composable and describe what you'd like it to do
n
Thanks, so first of all I have a vertical grid with some pieces in it:
Copy code
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun GameBoard(boardLetters: Array<Letter>) {
       Column(
        horizontalAlignment = Alignment.Start,
        verticalArrangement = Arrangement.Center,
    ) {
        LazyVerticalGrid(
            cells = GridCells.Fixed(7),
            verticalArrangement = <http://Arrangement.Top|Arrangement.Top>
        ) {
            items(boardLetters) { data ->
                Piece(data)
            }
        }
    }
}
I am trying to reload this gameboard when I click a FAB.
The piece composable looks like this:
Copy code
@Composable
fun Piece(data: Letter) {
    val viewModel: MainViewModel = viewModel()
    var enabled by remember { mutableStateOf(true) }
    var tileColor by remember { mutableStateOf(Color.White) }
    var visible by remember { mutableStateOf(true) }
    val scope = rememberCoroutineScope()
    val bestScore by viewModel.bestScore.observeAsState()
    val context = LocalContext.current
    val view = LocalView.current

    when (data.value) {
        1 -> tileColor = Color(0xff7189BF)
        2 -> tileColor = Color(0xffBF8B67)
        3 -> tileColor = Color(0xffFFAD60)
        4 -> tileColor = Color(0xffDF7599)
        5 -> tileColor = Color(0xffA275E3)
        8 -> tileColor = Color(0xff72D6C9)
        10 -> tileColor = Color(0xff655D8A)
    }

    val color by animateColorAsState(
        if (visible) tileColor else Color.White,
        animationSpec = tween(durationMillis = 1000)
    )

    val superscript = SpanStyle(
        baselineShift = BaselineShift.Superscript,
        fontSize = 12.sp,
        color = Color.White
    )

    Box(
        modifier = Modifier
            .fillMaxSize()
            .aspectRatio(1f)
            .padding(2.dp)
            .shadow(7.dp, shape = RoundedCornerShape(8.dp))
            .clip(shape = RoundedCornerShape(8.dp))
            .background(color)
            .clickable(enabled = enabled) {
                view.playSoundEffect(SoundEffectConstants.CLICK)
                viewModel.deleteResultString()
                for (v in data.name) {
                    viewModel.onNewLetterValue(data.value)
                    enabled = false
                }
                viewModel.onNewLetter(data.name)
                scope.launch {
                    DataStoreUtils.saveBestScore(context, "bestScoreKey", bestScore!!)
                }
                visible = !visible
            },
        contentAlignment = Alignment.Center
    ) {
        Text(
            text = buildAnnotatedString {
                append(data.name)
                withStyle(superscript) {
                    append("  ${data.value}")
                }
            }, style = TextStyle(Color.White, fontSize = 20.sp),
            fontWeight = FontWeight.ExtraBold
        )
    }
}
What happens is that when I click on a letter, it goes blank (white).
The problem is that when I update my data in order to refresh the composable and get a new set of letters, all the blank Pieces or tiles stay white.
I would need the visible variable to go back to true when I redraw the Composable.
I hope I am making sense!
a
The
Piece
composable is trying to own and control too much. Whenever you encounter a situation where you feel like you want to "reset" a composable, that's a sign that you should hoist the state you want to be able to reset out of `remember`s inside the composable itself, probably into a class.
Once you have a class that has a
Letter
and
enabled
,
tileColor
and
visible
properties, that same class can either have a
reset
method, or possibly even better, you can generate a new collection of instances of your class to pass to the
Piece
composables, and the act of generating that new board is all the reset you need.
chances are you don't want board pieces to be responsible for talking to a viewmodel or writing to a datastore either
👍 2
n
Brilliant, many thanks for this, I did feel like my poor
Piece
Composable had a lot going on it it!
a
at some point it'd be nice if we could get an IDE refactoring to transform
Copy code
@Composable
fun MyComposable() {
  var one by remember { mutableStateOf(initialOne) }
  var two by remember { mutableStateOf(initialTwo) }
into
Copy code
class MyComposableState(
  one: OneType,
  two: TwoType
) {
  var one by mutableStateOf(one)
  var two by mutableStateOf(two)
}

@Composable
fun MyComposable(
  state: MyComposableState = remember { MyComposableState(initialOne, initialTwo) }
) {
👍 3