https://kotlinlang.org logo
#compose
Title
# compose
b

brabo-hi

02/21/2022, 1:54 AM
Hi all, i have two custom SurfaceView representing 2 videos
Copy code
Row {
        AndroidView(factory = { context -> CustomView(context, "A") }, update = {})
        AndroidView(factory = { context -> CustomView(context, "B") }, update = {})
        Button(onClick = { swapViews() }) {
            Text(text = "Swap view")
        }
    }
swapViews()
should swap both views. but it looks like i have to manually remove each view from parent. Should i call
removeView()
in each
update{}
? here is the error i am getting
Copy code
java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.`
a

Adam Powell

02/21/2022, 2:25 AM
you might try something along the lines of emitting the `AndroidView`s in
key
composables
(sorry I tried writing up a snippet for it real quick and realized what I initially pasted for code was wrong 🙃 )
basically, do the same thing you would do when you iterate a collection and you want to be able to shuffle the elements keeping composable state
then
AndroidView
will do the rest
b

brabo-hi

02/21/2022, 2:29 AM
can you develop more when you said emitting the `AndroidView`s in 
key
 composables
a

Adam Powell

02/21/2022, 2:31 AM
Copy code
val items = remember { mutableStateListOf("A", "B") }
items.forEach { item ->
  key(item) {
    AndroidView(factory = { context -> CustomView(context, item) }, update = {})
  }
}
then reorder
items
to change the order the views appear in
b

brabo-hi

02/21/2022, 3:20 AM
here is an example
Copy code
var toggle by remember{ mutableStateOf(true)}
    Column {
        if (toggle) {
            AndroidView(factory = { context -> CustomView(context, "A") }, update = {})
            Divider()
            AndroidView(factory = { context -> CustomView(context, "B") }, update = {})
        } else {
            AndroidView(factory = { context -> CustomView(context, "B") }, update = {})
            Divider()
            AndroidView(factory = { context -> CustomView(context, "A") }, update = {})
        }
    }
    Button(onClick = { toggle = !toggle }) {
        Text(text = "Swap view")
    }
i put
Divider()
just to say that i am many views between. The first time
toggle == true
if the
A
…`B` The next time
toggle == false
it tries to replace
A
with
B
but it finds that
B
hasn’t been completely removed. that’s where i am having issue
just to complete. Views
A
and
B
are displayed at the same time but at different position depending on the toogleState
@Adam Powell any suggestion?
a

Adam Powell

02/21/2022, 10:46 PM
is
CustomView(context, "A")
not a constructor call here? I would expect the code above to create two new views and remove the old two views, abandoning them to the garbage collector each time
toggle
switches value
b

brabo-hi

02/22/2022, 12:06 AM
yes indeed. But i believe the issue is the fact that is recomposition, the system tries to place
ViewB
before removing it. Which technically placed the same view at two places at the same times
First composition • place ViewA at position X • place ViewB at position Y Second composition • remove ViewA at position X • place ViewB at position X
this is where the issue is since ViewB is still at position Y
• remove ViewB at position Y • place viewA at position Y
a

Adam Powell

02/22/2022, 12:39 AM
that's not how that works. It's not the same views when you have an if/else like that
you can verify with this:
Copy code
Column {
    var toggle by remember { mutableStateOf(false) }
    if (toggle) {
        AndroidView(
            { View(it).apply { setBackgroundColor(0xff00ff00.toInt()); println("created $this") } },
            Modifier.size(48.dp)
        )
        AndroidView(
            { View(it).apply { setBackgroundColor(0xffff0000.toInt()); println("created $this") } },
            Modifier.size(48.dp)
        )
    } else {
        AndroidView(
            { View(it).apply { setBackgroundColor(0xffff0000.toInt()); println("created $this") } },
            Modifier.size(48.dp)
        )
        AndroidView(
            { View(it).apply { setBackgroundColor(0xff00ff00.toInt()); println("created $this") } },
            Modifier.size(48.dp)
        )
    }
    Button(onClick = { toggle = !toggle }) {
        Text("Switch")
    }
}
each time you click the button you'll see it print two new views with two new identity hash codes
contrast with this:
Copy code
val list = remember { mutableStateListOf(0xff00ff00.toInt(), 0xffff0000.toInt()) }
list.forEach { item ->
    key(item) {
        AndroidView(
            { View(it).apply { setBackgroundColor(item); println("created $this") } },
            Modifier.size(48.dp)
        )
    }
}

Button(onClick = { list.reverse() }) {
    Text("Switch")
}
I'm going to guess there's something fishy going on with that custom view you're working with
8 Views