I'm setting a Compose State var based on StateFlow...
# compose
r
I'm setting a Compose State var based on StateFlow coming from a ViewModel and based on that State var, setting a regular var. This works until config changes at which point, the regular var is not getting updated. Am I missing something or is this a bug with Compose? Code in thread
Copy code
@Composable
 fun ScreenContent(
     vm: MyViewModel,
     toggleTheme: () -> Unit,
 ) {
     val viewState by vm.dataAvailable.collectAsStateWithLifecycle()
 
     var dataAvailable by remember { mutableStateOf(false) }
 
     val text = if (dataAvailable) {
         "Data available"
     } else {
         "Data not available"
     }
 
     Column(
         modifier = Modifier
             .fillMaxSize()
             .background(MaterialTheme.colorScheme.background),
         verticalArrangement = Arrangement.Center,
         horizontalAlignment = Alignment.CenterHorizontally
     ) {
         when (viewState) {
             MyViewModel.ViewState.Data -> {
                 dataAvailable = true
 
                 Text(text, color = MaterialTheme.colorScheme.onBackground)
                 Button(onClick = toggleTheme) {
                     Text("Change theme")
                 }
             }
 
             MyViewModel.ViewState.Loading -> {
                 CircularProgressIndicator()
             }
         }
     }
 }
Copy code
class MyViewModel : ViewModel() {
     private val _dataAvailable: MutableStateFlow<ViewState> = MutableStateFlow(ViewState.Loading)
     val dataAvailable: StateFlow<ViewState> = _dataAvailable
 
     init {
         viewModelScope.launch {
             delay(1000)
             _dataAvailable.value = ViewState.Data
         }
     }
 
     sealed class ViewState {
         data object Loading : ViewState()
         data object Data : ViewState()
     }
 }
g
I found this surprising at first, but after thinking a bit about it, I think of it this is kind of a side effect, so maybe you need to change
dataAvailable
from within a
SideEffect
. I know
SideEffect
fixes it, but I'm curious to know if that's the right thing to do here. I personally would have simply made
text
depend on
viewState
rather than using this sort of indirection, but that's not relevant to your question.
the configuration change bit is not relevant by the way. It'll happen on the first composition if you simply set
viewState
to a constant value
Copy code
val viewState = MyViewModel.ViewState.Data
s
Why does this
dataAvailable
exist in the first place? Do you want it to survive config changes? If yes, you want rememberSaveable. Also, never do such backwards writes in composition like that, always look into using the side effect apis when you want to do... a side effect in composition like assigning a value. The docs have a dedicated section on this https://developer.android.com/develop/ui/compose/performance/bestpractices#avoid-backwards
g
The backward write is the bit I found interesting, I would have expected a recomposition since the code is changing a MutableState (
dataAvailable
), but that doesn't happen, while the doc you linked says otherwise. As I mentioned in my messages I understand why the snippet could cause problems, but I've always seen State objects as something special that didn't need this kind of special treatment. But I agree that this is not the best way to write a compostable, as I also mentioned in my previous messages.
r
I modified the snippet to make it easier to follow the code. The side effect needs to be propagated up the function chain in the real usage. Similar to gmz, my understanding was that changing a compose State var's value will trigger recomposition.
s
I've read somewhere that the recomposition tracking only starts after the first composition. So since you're doing this on the first frame before any successful composition has completed, that tracking hasn't started yet. So tl;dr just don't do side effects in composition like that. You're only allowing mistakes like this to happen.
a
Doesn't it help if you use rememberSaveable instead, which is handling config changes?
Copy code
var dataAvailable by rememberSaveable { mutableStateOf(false) }
EDIT! I would also have thought that your code should have worked though 🤷
r
That should help. And I think there are other ways to solve it. I think the issue is recomposition not happening when expected. Stylianos - I tried finding that information but couldn't. I might create an issue as it seems like I'm not the only one confused with how recomposition is supposed to work and see if I can get an official explanation
s
In any case, you are doing something which you are not supposed to do, which is covered very specifically in the docs which I linked. On top of that, you are also doing a side effect in composition, which is also heavily discouraged. With that said, you can still file a bug for the docs to provide more information about what happens here.
r
Oh I missed that link. Will read that first. Thank you