I am working on an Android application in which I would like to filter both parent and child list.
To make it more clear: Let us consider that there are two data classes and one list.
data class User(val id:String, val name: String, val devices: List<Devices>)
data class Device(val deviceId:String, val manufacturer: String, val status:Int)
val list: List<User> = <Initialisation here>
The UI hierarchy will be something like below.
(Parent)User 1
-> (Child) Device 1 (Status -> 0)
-> (Child) Device 2 (Status -> 0)
-> (Child) Device 3 (Status -> 1)
(Parent)User 2
-> (Child) Device 4 (Status -> 1)
-> (Child) Device 5 (Status -> 0)
-> (Child) Device 6 (Status -> 1)
(Parent)User 3
-> (Child) Device 4 (Status -> 0)
-> (Child) Device 5 (Status -> 0)
-> (Child) Device 6 (Status -> 0)
And, I want to show the header and child only if includes the status "1".
(Parent)User 1
-> (Child) Device 3 (Status -> 1)
(Parent)User 2
-> (Child) Device 4 (Status -> 1)
-> (Child) Device 6 (Status -> 1)
What I have tried ?
list?.filter { user -> user.devices.any { device -> device.status == 1}}
The above snippet ignores "User 3"
but still fetches all the devices(with status 1 & 0) of "User 1"
and "User 2"
.
Please help me to achieve this.
CodePudding user response:
You want to filter the users list based on a condition of the devices of the user and also filter the devices list
list?.mapNotNull { user ->
val devices = user.devices.filter { it.status == 1}
if (devices.isNotEmpty()) {
user.copy(devices = devices)
} else null
}
That will return you a new list where every user has devices with status one, and only the devices status one are considered for such a user.
CodePudding user response:
Your code only filters your list to the users that have at least one device with the correct status - so those are the users you'll be displaying. You still need to filter their devices
lists:
list?.filter { user ->
user.devices.any { it.status == 1}
}?.map { user ->
user.copy(devices = user.devices.filter { it.status == 1 })
}
That gives you a new set of User
s containing only the required Device
s.
If you just want a lookup of the required devices, you could do
list?.filter { user ->
user.devices.any { it.status == 1 }
}?.associateWith { user ->
user.devices.filter { it.status == 1 }
}
which gives you a map of User
s (unchanged) to a list of Device
s with that status. Or you could do the filtering the other way around if you like:
list?.associateWith { user -> user.devices.filter { it.status == 1 } }
?.filterValues { it.isNotEmpty() }
Lots of other ways too, like using mapNotNull
and returning null if there isn't a valid device, instead of doing a separate filtering step. Whatever you like best!
CodePudding user response:
You can start by filtering out the devices with status 0
for each user.
list?.map { user ->
user.copy(devices = user.devices.filter { it.status == 1 } )
}
Now if you don't want users with empty devices
list, you can filter them too.
list?.map { user -> user.copy(devices = user.devices.filter { it.status == 1 } ) }
?.filter { it.devices.isNotEmpty() }
CodePudding user response:
As others have mentioned, you need to filter not only users but also need to filter devices and update each user's device list with the required status.
I would also extract the device's status check to the data class to encapsulate this logic to make it more clean and readable.
data class Device(val deviceId: String, val manufacturer: String, val status: Int) {
fun isAvailable() = status == 1
}
fun filterUsersByDeviceStatus(users: List<User>) = users.fold(emptyList<User>()) { accumulator, user ->
user.devices
.filter(Device::isAvailable)
.takeIf(List<Device>::isNotEmpty)
?.let { accumulator user.copy(devices = it) }
?: accumulator
}