How to trigger recomposition when modify the parent data using CompositionLocal

4.6k Views Asked by At

when I use CompositionLocal, I have got the data from the parent and modify it, but I found it would not trigger the child recomposition.

I have successfully change the data, which can be proved through that when I add an extra state in the child composable then change it to trigger recomposition I can get the new data.

Is anybody can give me help?

Append

code like below


data class GlobalState(var count: Int = 0)

val LocalAppState = compositionLocalOf { GlobalState() }

@Composable
fun App() {
    CompositionLocalProvider(LocalAppState provides GlobalState()) {
        CountPage(globalState = LocalAppState.current)
    }
}

@Composable
fun CountPage(globalState: GlobalState) {
    // use it request recomposition worked
//    val recomposeScope = currentRecomposeScope
    BoxWithConstraints(
        contentAlignment = Alignment.Center,
        modifier = Modifier
            .fillMaxSize()
            .clickable {
                globalState.count++
//                recomposeScope.invalidate()

            }) {
        Text("count ${globalState.count}")
    }
}

I found a workaround is using currentRecomposable to force recomposition, maybe there is a better way and pls tell me.

3

There are 3 best solutions below

3
On BEST ANSWER

I am not sure why you are using compositionLocalOf in this way.

Using the State hoisting pattern you can use two parameters in to the composable:

  • value: T: the current value to display.
  • onValueChange: (T) -> Unit: an event that requests the value to change where T is the proposed new value.

In your case:

data class GlobalState(var count: Int = 0)

@Composable
fun App() {

    var counter by remember { mutableStateOf(GlobalState(0)) }
    CountPage(
        globalState = counter,
        onUpdateCount = {
                counter = counter.copy(count = counter.count +1)
            }
        )
}

@Composable
fun CountPage(globalState: GlobalState, onUpdateCount: () -> Unit) {
   
    BoxWithConstraints(
        contentAlignment = Alignment.Center,
        modifier = Modifier
            .fillMaxSize()
            .clickable (
                onClick = onUpdateCount
            )) {
        Text("count ${globalState.count}")
    }
}
2
On

The composition local is a red herring here. Since GlobalScope is not observable composition is not notified that it changed. The easiest change is to modify the definition of GlobalState to,

class GlobalState(count: Int) {
   var count by mutableStateOf(count)
}

This will automatically notify compose that the value of count has changed.

0
On

You can declare your data as a MutableState and either provide separately the getter and the setter or just provide the MutableState object directly.

internal val LocalTest = compositionLocalOf<Boolean> { error("lalalalalala") }
internal val LocalSetTest = compositionLocalOf<(Boolean) -> Unit> { error("lalalalalala") }

@Composable
fun TestProvider(content: @Composable() () -> Unit) {
  val (test, setTest) = remember { mutableStateOf(false) }

  CompositionLocalProvider(
    LocalTest provides test,
    LocalSetTest provides setTest,
  ) {
    content()
  }
}

Inside a child component you can do:

@Composable
fun Child() {
  val test = LocalTest.current
  val setTest = LocalSetTest.current

  Column {
    Button(onClick = { setTest(!test) }) {
      Text(test.toString())
    }
  }
}