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

Lilly

12/19/2020, 2:04 PM
Is there any difference between
Copy code
val devices = remember { mutableStateListOf<DeviceModel>() }

    LaunchedEffect(subject = viewModel.devices, block = {
        viewModel.devices.collect { model ->
            if (!devices.contains(model)) devices.add(model)
        }
    })
and
Copy code
val devices by produceState(initialValue = mutableStateListOf<DeviceModel>(), viewModel) {
        viewModel.devices.collect { model ->
            if (!value.contains(model)) value.add(model)
        }
    }
a

allan.conda

12/19/2020, 2:17 PM
check the source and you’ll see how it’s different
👍 1
second one only runs a LaunchedEffect once per composition even if devices state changed, it seems
l

Lilly

12/19/2020, 3:55 PM
🤦‍♀️ my bad
@allan.conda Are you aware of an elegant way to only have unique objects in the
mutableStateList
?
👍 1
d

Dominaezzz

12/19/2020, 5:09 PM
The second one is slightly less efficient. Or at least add a bit of unnecessary complexity.
Because you never change the state object.
1
Consider using
List<DeviceModel>
for the second case, then they become more comparable.
👍 1
a

Adam Powell

12/19/2020, 5:12 PM
or
PersistentList
, I think
MutableStateList
uses the persistent collections under the hood
1
d

Dominaezzz

12/19/2020, 5:12 PM
To keep the objects unique you can use a
LinkedHashSet
.
l

Lilly

12/20/2020, 4:14 AM
Thanks guys. @Dominaezzz @Adam Powell When I use
linkedSetOf
or
mutableListOf
for second snippt, no items are shown which makes sense, because I don't do
value = device
so
devices
doesn't change but when I use
mutableStateListOf
and do
value.add(device)
,
devices
changed. Or am I wrong? Btw...it seems that
PersistentList
doesn't exist, same for
MutableStateList
?!
a

Adam Powell

12/20/2020, 4:22 AM
PersistentList comes from the kotlinx.collections.immutable artifact; by MutableStateList I mean the object returned by mutableStateListOf. The analogy here is
var list = mutableListOf()
- you don't need both the list and the reference to the list to be mutable
Pick one;
produceState
is about that leading
var
Your first snippet is about the collection being mutable but the reference to it immutable; probably what you want here
l

Lilly

12/20/2020, 4:27 AM
Waht do you mean with
produceState
 is about that leading 
var
a

Adam Powell

12/20/2020, 4:30 AM
produceState
is just a convenience around
Copy code
val state = remember { mutableStateOf(initialValue) }
LaunchedEffect {
  state.block()
}
return state
It gives you one observable mutable variable that the provided block feeds
But you don't want one observable mutable value, you want one observable mutable list
l

Lilly

12/20/2020, 4:33 AM
ok I guess I got it. In second snippet both the list and the reference is mutable
a

Adam Powell

12/20/2020, 4:33 AM
Yep
l

Lilly

12/20/2020, 4:34 AM
The first one fits definetely better here 🙂
a

Adam Powell

12/20/2020, 4:34 AM
I think so too 🙂
l

Lilly

12/20/2020, 4:36 AM
But I still have the problem with duplicates 😕
a

Adam Powell

12/20/2020, 5:00 AM
we've only got
mutableStateListOf
and
mutableStateMapOf
for snapshot-observable collections at the moment, we don't have a set. If you combine
PersistentSet
from kotlinx.collections.immutable and
produceState
from compose you should have what you need
which is to say,
PersistentSet
is an efficient immutable set type that potentially returns a new set instance whenever you add or remove from it, and if you store that reference in a
mutableStateOf
holder, you get the observability you need with an efficient immutable set implementation
l

Lilly

12/21/2020, 3:56 AM
@Adam Powell Couldn't I also add the device directly to the LazyColumn instead of adding it to a list/set first? 🤔
a

Adam Powell

12/21/2020, 4:01 AM
a LazyColumn (and any composable function, really) just reads from state you supply. Once this patch lands, you can use any backing state type you like, but there will still be no concept of adding to a data structure private to/owned by LazyColumn itself https://android-review.googlesource.com/c/platform/frameworks/support/+/1530327
l

Lilly

12/21/2020, 9:43 PM
@Adam Powell Alright thanks. Lets say a device is collected which is already in list/set but I would like to replace the new one with the existing one because the new one has updates fields, how would you achieve this?
I could remove the existing one and add the new one but this would trigger recompose twice or?
a

Adam Powell

12/21/2020, 10:42 PM
When you add or remove from a persistent collection, it returns a new immutable collection instance that copies as little as possible from the original while sharing internal structure. Once you perform the manipulation you need, assign the new collection to a MutableState object once.
Either way though, if you want to perform several snapshot state updates atomically with regard to one another, see
withMutableSnapshot
. Any changes you make within an explicit mutable snapshot all happen together or not at all, and changes aren't made visible to other threads unless you commit all changes together or otherwise pass the snapshot between threads explicitly.
Compose will only recompose for full snapshot commits, not for partial snapshots.
l

Lilly

12/21/2020, 11:12 PM
I still get duplicates:
Copy code
val devices = remember { mutableStateOf(persistentListOf<DeviceModel>()) }

    LaunchedEffect(subject = viewModel.deviceFlow, block = {
        viewModel.deviceFlow.collect { model ->
            if (!devices.value.contains(model)) {
                devices.value = devices.value.add(model)
            }
        }
    })

    BluetoothDeviceListComponent(devices.value, onConnectDevice)
What do I wrong? :/
d

Dominaezzz

12/21/2020, 11:15 PM
Does
DeviceModel
have an
equals
implementation?
l

Lilly

12/21/2020, 11:18 PM
Copy code
data class DeviceModel(val bluetoothDevice: BluetoothDevice, val rssi: Int)
data classes generate equals functions right?
a

Adam Powell

12/21/2020, 11:20 PM
They do, so if the rssi keeps changing, they aren't equal and it will look like a new object to that code 🙂
Remove the element with the same unique id, or use a map with the unique id as the key
l

Lilly

12/22/2020, 1:03 AM
@Dominaezzz Thanks for the hint. To just get rid of the duplicates I only had to override the equals function:
Copy code
data class BluetoothDeviceModel(val bluetoothDevice: BluetoothDevice, val rssi: Int) {

        override fun equals(other: Any?): Boolean {
            if (other is BluetoothDeviceModel) {
                return this.bluetoothDevice.address == other.bluetoothDevice.address
            }
            return false
        }
   }
Additionally when I want to update the list with newer devices there are multiple options. To preserve the order of the items I came up with this solution:
Copy code
val devices = remember { mutableStateListOf<DiscoverBluetoothDevicesUseCase.BluetoothDeviceModel>() }

    LaunchedEffect(subject = viewModel.deviceFlow, block = {
        viewModel.deviceFlow.collect { model ->
            withMutableSnapshot {
                devices.indexOf(model).takeIf { it != -1 }?.let { i ->
                    devices.remove(devices[i])
                    devices.add(i, model)
                } ?: devices.add(model)
            }
        }
    })
@Adam Powell Is this implementation legit regarding performance and do I use
withMutableSnapshot
properly?
a

Adam Powell

12/22/2020, 1:47 AM
That'll do fine
👍 1
15 Views