Home > Software design >  Android - How to filter child list?
Android - How to filter child list?

Time:09-16

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 Users containing only the required Devices.

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 Users (unchanged) to a list of Devices 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
}
  • Related