This message was deleted.
# compose
s
This message was deleted.
d
Hi Lilly. good questions. I think this is an area where you will get different answers from different folks; as this comes down to software architecture. I would suggest that you consider a few approaches and pick what works best for you and your application. That said; here are my thoughts: • I've also seen people exposing complete, structured
State
objects to their Views. Somtimes there are situations of truly
sealed
state where this is the only approach that makes sense, but I try to have as much destructuring as possible already done by the ViewModel. Reasons for this are that I believe in general the View layer should be as thin as possible: if you come to write a different View layer for another platform (say, Web) then you want to be rewriting as little code as possible. The ViewModel should spell out the View as explicitly as possible.
• While you can write a State structure that's purely concerned with View, having a lot of destructing done in the View also feels like an invitation to include some level of business logic at the point of destructuring. If you're a lone developer on the project you can be sensitive to this and avoid it; but too often your colleagues may not fully grasp the ViewModel / View separation and start to introduce business logic along with the
State
destructure.
l
Thanks Chris, I have cut down my question
👍 1
d
• By having the ViewModel break down the different parameters of your View and expose them as separate
Flows
you also keep changes to the View more granular, which can lead to better performance overall.
I agree it's a good call to not have Android specific types in your domain model.
I would consider having a repeatable
DeviceViewModel
that gets instanced for every device in your list
so that the ViewModel layer is 'symmetric' to the UI hierarchy
and in that way, the
DeviceViewModel
can expose a
StateFlow
for each of the changing data.
Hopefully those thoughts help you on your way to a solution, but would invite others to comment to give you some different perspectives...
l
you also keep changes to the View more granular, which can lead to better performance overall
I had exactly this granularity in mind when I was asking for the right approach. If I have just one State object, the problem is that there have to be hust one field that changes quite often to trigger a new object creation
d
@Lilly After you edited the question, I have another direct suggestion that I use in my own code, for objects with dynamic data like this.
That is; don't be afraid to use
StateFlow
for the actual fields of your domain Model object.
I would model:
Copy code
data class DeviceModel(
    val deviceName: String,
    val deviceAddress: String,
    val rssiFlow: StateFlow<Int>
)
When you get an update from hardware ('service layer' in clean architecture philosophy), you'd want to locate your domain model object for the device e.g. via
deviceAddress
as a key, then update the
rssiFlow
with the newly updated value.
...in your ViewModel you'll map the data to a displayable value like:
Copy code
val rssiDescriptionFlow : StateFlow<String> = model.rssiFlow.map { rssi -> "${rssi} db" }.stateIn(scope, Eagerly, "- db")
l
I understand your intent. But my UI needs a listOf<DeviceModel>.
d
...then on the Compose side you're doing e.g.
val deviceViewModel.rssiDescriptionFlow.collectAsState()
I would say your UI (Composable funciton) should have a
List<DeviceViewModel>
- where each of those
DeviceViewModel
's prepares the presentation values for a
DeviceModel
that it, in turn, holds
The key part is that the View is always directly accessing a ViewModel and not the Model directly.
Again this is a question of software architecture preference, it's not a hard rule.
But I would say that MVVM in the way I'm describing is a pretty tried and tested approach.
l
ok fuck I'm pretty sure we talk at cross purposes. Is it cool for you when I delete this thread and we discuss this via private message. I will keep it short
d
Sure.
r
Now after reading this long thread I am curious about the question actually. Because I am also facing some troubles in regard to managing the state properly.
👍 1
l
Sorry guys, I messed it up so I tried to remove the thread but didn't know that it's not possible to remove the whole thread. My initial question was: I have a question about mutable objets and recomposition. I have a scanner screen that scans for nearby bluetooth classic devices. In my old approach my presenter/view model exposes 2 states:
Copy code
// Every time a new BluetoothDevice is discovered, it's wrapped into this class.
// It happens quite frequently, that the same device is discovered multiple times but the rssi changes
data class BluetoothDeviceWrapper(val device: BluetoothDevice, val rssi: Int) {

    override fun equals(other: Any?): Boolean {
        if (other is BluetoothDeviceWrapper) {
            return this.device.address == other.device.address
        }
        return false
    }
}


val isScanning = mutableStateOf(false)
val devices = mutableStateListOf<BluetoothDeviceWrapper>()
To reflect rssi changes when an equal device arrives, I remove the existing device from the devices list and re-add the new device. But I don't like to expose such a type that is so tied to android. Instead I want to expose a DeviceModel:
Copy code
data class DeviceModel(
    val deviceName: String,
    val deviceAddress: String,
    VAR rssi: Int
)
Here I have multiple questions: I have seen, that many users expose just one big state object to the UI, so I'm wondering if I should do the same instead of exposing isScanning and devices seperately? This might look like this:
Copy code
data class ScannerState(
    VAR isScanning: Boolean,
    val devices: MutableList<DeviceModel>
)
But then I'm wondering if I can handle the duplicates like stated in the comment of the first code snippet above in a more elgant way than removing and re-adding the devices. Is it possible to update the rssi field so that compose is notified about this change and recompose? Same question for the isScanning field. When I make DeviceModel or ScannerState immutable this implies that I have to create a new object for every change of isScanning isn't that a problem or bad practise to create a new object so often? The frequency how often ScannerState is created can be reduced when I handle isScanning and devices separately, so I'm not sure whats the right approach. Another problem AFAIK is the MutableList. I doubt compose can handle mutable lists so I'm forced to create a new immutable list for every new discovered device. @Rafiul Islam @JulianK
👍 1
I will cut down these questions and create a separate thread for them