I am creating a Android Application which show list of Stores. The main Recyclerview shows the list of Store
and every store object has SortingData
which holds multiple fields like minimumDistance,rating
etc.
By user selection i get a list of selected tags
which size varies on the base of user selection and want to sort the main list of stores by it.
Every selected tag holds a propery isAscending which shows that it should be sorted in Ascending or Descending order. Lets say Rating will be Descending and Minimum Distance will be ascending and so on.
I have written a custom comparator to do so, to avoid multiple if conditions inside a loop but this comparator has issues. Like, its sorting the intergers based on first digit only doubles are also not sorted well.
Below is my code
data class Store(
val name: String,
val sortingData: SortingData,
val status: String
) {
}
data class SortingData(
val averageProductPrice: Int,
val bestMatch: Double,
val deliveryCosts: Int,
val distance: Int,
val minCost: Int,
val newest: Double,
val popularity: Double,
val ratingAverage: Float
)
data class SortTag(var text: String, var key:String,var isSelected: Boolean,var isAscending:Boolean) {
}
Function
fun sortListByAdditionalTags(
list: MutableList<Store>>, selectedTags: List<SortTag>
): MutableList<Store> {
/*
Best Match -> Descending highest value on top
Newest -> Descending highest value on top
Rating Average -> Descending highest value on top
Distance -> Ascending lowest value on top
Popularity -> Descending highest value on top
Average Product Price -> Ascending lowest value on top
Delivery cost -> Ascending lowest value on top
Min cost-> Ascending lowest value on top
*/
var sorted = list
selectedTags.forEach {
sorted = list.sortedWith(
comparator = AdditionalSortComparator(
it.key,
it.isAscending
)
) as MutableList< Store
>
}
return sorted
}
Custom Sort Comparator
class AdditionalSortComparator(
private val sortProperty: String? = null,
private val isAscending: Boolean
) : Comparator<Store> {
override fun compare(o1: Store?, o2: Store?): Int {
val sortingData =
Store::class.declaredMemberProperties.firstOrNull { it.name == "sortingData" }
val s1: sortingData = o1?.let { sortingData?.get(it) } as sortingData
val s2: sortingData = o2?.let { sortingData?.get(it) } as sortingData
val calledVariable = SortingData::class.declaredMemberProperties.firstOrNull { it.name == sortProperty }
return if (calledVariable != null) {
if (calledVariable.get(s1) is Int) {
val valueFirst = calledVariable.get(s1) as Int
val valueSecond = calledVariable.get(s2) as Int
if (isAscending) valueFirst - valueSecond else valueSecond - valueFirst
} else if (calledVariable.get(s1) is Float) {
val valueFirst = calledVariable.get(s1) as Float
val valueSecond = calledVariable.get(s2) as Float
if (isAscending) valueFirst - valueSecond else valueSecond - valueFirst
} else if (calledVariable.get(s1) is Double) {
val valueFirst = calledVariable.get(s1) as Double
val valueSecond = calledVariable.get(s2) as Double
if (isAscending) abs(valueFirst-valueSecond) else abs(valueSecond-valueFirst)
}
if (isAscending) calledVariable.get(s1).toString()
.compareTo(calledVariable.get(s2).toString())
else calledVariable.get(s2).toString()
.compareTo(calledVariable.get(s1).toString())
} else {
val idProperty = Store::name
val valueFirst = idProperty.get(o1)
val valueSecond = idProperty.get(o2)
if (isAscending) valueFirst.compareTo(valueSecond) else valueSecond.compareTo(valueFirst)
}
}
}
The dependency i used for Kotlin Reflection is
implementation("org.jetbrains.kotlin:kotlin-reflect:1.7.10")
Can somebody please help me out with this, how i can achieve this functionality in an efficient and correct manner?
Never cast a List to a MutableList. It is inherently unsafe and can cause crashes at runtime. Even if it doesn't now, it could crash in the future if the standard library changes what kind of list implementation it's using under the hood. (If you truly have to have a MutableList, use toMutableList()
to create a mutable copy)
You should use MutableLists with a RecyclerView anyway...it can lead to all kinds of tricky bugs.
The above alone might be part of your issue. The rest probably has to do with using reflection and string values of your non-string comparable properties.
Here's how I would do it with minimal reflection.
First, keep a map of the tags to the associated properties. This is the only line of reflection you need to do your task.
data class SortingData(
val averageProductPrice: Int,
val bestMatch: Double,
val deliveryCosts: Int,
val distance: Int,
val minCost: Int,
val newest: Double,
val popularity: Double,
val ratingAverage: Float
) {
companion object {
val propertiesByName = SortingData::class.declaredMemberProperties
.associate{
@Suppress("UNCHECKED_CAST") // safe because they're all numbers
it.name to it as KProperty1<SortingData, Comparable<*>>
}
}
}
Then use the comparator builder functions to construct your builder:
fun sortedListByAdditionalTags(list: List<Store>, selectedTags: List<SortTag>): List<Store> {
// properties where "isAscending" actually means descending (big numbers first)
val invertedSortProperties = listOf(
SortingData::bestMatch,
SortingData::newest,
SortingData::ratingAverage,
SortingData::popularity
)
var comparator: Comparator<Store> = compareBy { 0 } // no-op default
for (sortTag in selectedTags) {
val property = SortingData.propertiesByName[sortTag.key] ?: continue
val ascending = if (property in SortingData.invertedSortProperties) !sortTag.isAscending else sortTag.isAscending
val function: (Store) -> Comparable<*> = { property.invoke(it.sortingData) }
comparator = if (ascending) comparator.thenBy(function) else comparator.thenByDescending(function)
}
return list.sortedWith(comparator)
}