1

Is there a way to reparent a Composable without it losing the state? The androidx.compose.runtime.key seems to not support this use case.

For example, after transitioning from:

// This function is in the external library, you can not 
// modify it!
@Composable
fun FooBar() {
    val uid = remember { UUID.randomUUID().toString() }
    Text(uid)
}
Box {
  Box {
    FooBar()
  }
}

to

Box {
  Row {
    FooBar()
  }
}

the Text will show a different message.


I'm not asking for ways to actually remember the randomly generated ID, as I could obviously just move it up the hierarchy. What I want to archive is the composable keeping its internal state.

Is this possible to do without modifying the FooBar function?

The Flutter has GlobalKey specifically for this purpose. Speaking Compose that might look something like this:

val key = GlobalKey.create()
Box {
  Box {
    globalKey(key) {
      FooBar()
    }
  }
}
Box {
  Row {
    globalKey(key) {
      FooBar()
    }
  }
}
AChep
  • 787
  • 6
  • 10

2 Answers2

1

This is now possible with

movableContentOf

See this example:

val boxes = remember {  
    movableContentOf {  
        LetterBox(letter = 'A')  
        LetterBox(letter = 'B')  
    }  
}  
  
Column(  
    horizontalAlignment = Alignment.CenterHorizontally  
) {  
    Button(onClick = { isRow = !isRow }) {  
        Text(text = "Switch")  
    }  
    if (isRow) {  
        Row(  
            Modifier.weight(1f),  
            verticalAlignment = Alignment.CenterVertically  
        ) {  
            boxes()  
        }  
    } else {  
        Column(  
            Modifier.weight(1f),  
            verticalArrangement = Arrangement.Center  
        ) {  
            boxes()  
        }  
    }  
}
AChep
  • 787
  • 6
  • 10
  • Very good! It's especially makes sense if the "movable" element is AndroidView since we don't want the view be recreated every time when it changes the place in hierarchy. – Alexander Skvortsov Apr 25 '22 at 17:41
0

remember will store only one value in the same view. The key in Compose has a very different purpose: if the key passed to remember has a different value from the last recomposition, it means that the old value is no longer relevant and must be recomputed.

There is no direct equivalent of Flutter keys in Compose.

You can simply declare a global variable. In case you need to change it, wrap it with a mutable state, so changes will update your view.

var state by mutableStateOf(UUID.randomUUID().toString())

I'm not sure if that the same what GlobalKey does, in any case it's not the best practice, just like any other global variable.


If you need to share some data between views, it is much cleaner to use view models.

@Composable
fun TestScreen() {
    val viewModel = viewModel<SomeViewModel>()
    Column {
        Text("TestScreen text: ${viewModel.state}")
        OtherView()
    }
}

@Composable
fun OtherView() {
    val viewModel = viewModel<SomeViewModel>()
    Text("OtherScreen text: ${viewModel.state}")
}

class SomeViewModel: ViewModel() {
    var state by mutableStateOf(UUID.randomUUID().toString())
}

The hierarchy topmost viewModel call creates a view model - in my case inside TestScreen. All children that call viewModel of the same class will get the same object. The exception to this is different destinations of Compose Navigation, see how to handle this case in this answer.

You can update the mutable state value, and it will be reflected on all views using that model. Check out more about state in Compose.

When the view that created the view model is removed from the view hierarchy, the view model is also freed, so a new one will be created next time.

Phil Dukhov
  • 67,741
  • 15
  • 184
  • 220
  • 1
    Unfortunately, this will not help in case I want to use the `AnimatedVisibility` and still want to see the animation even after reparenting the view into another container. Because its internal state will be erased. – AChep Sep 13 '21 at 10:29
  • @AChep You can store animated visibility state with `MutableTransitionState`. If this advice doesn't help you, I suggest rephrasing your question so that it is clearly understood by people without an understanding of Flutter keys. – Phil Dukhov Sep 13 '21 at 10:54
  • Added an example of how the `GlobalKey` might have looked in Compose. – AChep Sep 14 '21 at 14:08
  • 1
    @AChep There's no such functionality in compose. You can put whole state of the composable in a view model or an other class, and then share it between multiple places. – Phil Dukhov Sep 14 '21 at 16:13