Kotlin: How can I reduce child arrays into a single array? - android

I have a pice of code in Swift that reduces a list of TVSchedule objects into an array of TVMatch pobjects. Each TVSchedule, has a property called events, that is a list of TVMatches.
The code in swift is the following:
var matches: [TVMatch] {
let slots = timeSlots.reduce(into: [TVMatch]()) { (result, schedule) in
result.append(contentsOf: schedule.events)
}
return slots
}
I'm trying to do the same reduce in Kotlin and the code I have is the following:
val matches: ArrayList<TVMatch>
get() {
val slots = timeSlots.fold(arrayListOf<TVMatch>()) { result, schedule ->
result.addAll(schedule.events)
}
return slots
}
However, the Kotlin code gives me a type error, and does not compile. What is the problem here?

addAll returns a boolean, but the return value of the fold-operation should be of same type as the given initial object (in this case ArrayList).
You can solve that one easily by just adding result after your addAll-statement, e.g.:
result.addAll(schedule.events)
result // this is now the actual return value of the fold-operation
Alternatively just use apply or similar instead:
result.apply {
addAll(schedule.events)
} // result is the return value then
Note that you can actually simplify altogether using flatMap to just (side-note: if you use this approach the matches are evaluated only once of course, but flatMap is the star here anyway ;-))):
val matches = timeSlots.flatMap { it.events } // this is a new list! (note, if you do several mappings in a row, you may want to use timeSlots.asSequence().flatMap { }.map { }.toList() / or .toMutableList() instead
Alternatively if you really require the matches to be of type ArrayList, use flatMapTo instead:
val matches = timeSlots.flatMapTo(ArrayList()) { it.events }
You can of course keep the get() if you must, or just move the getting of the matches to its own function, e.g.:
fun getMatches() = timeSlots.flatMapTo(ArrayList()) { it.events }

Am I crazy, or can't you just replace the code with
val matches: List<TVMatch>
get() = timeSlots.flatMap { schedule -> schedule.events }
?

Related

How To Return Nested Variable?

I don't know how to RETURN variable from the following function.
Here is the code...
downloadData.setOnClickListener {
val handler = Handler(Looper.getMainLooper())
handler.post {
val fetchData =
FetchData("http://localhost/xampp/CRM/PHP/show_contacts_db.php")
if (fetchData.startFetch()) {
if (fetchData.onComplete()) {
val result = fetchData.data.toString()
Log.i("FetchData", result)
val companyName = result.substringAfter("Name: ").substringBefore(";")
showContactName.text = "${companyName}"
val companyNumber = result.substringAfter("Number: ").substringBefore(";")
showContactNumber.text = "${companyNumber}"
}
}
}
}
companyName and companyNumber needed to be returned so I can use it in other places.
When I Try to use Return companyNumber I have a message that "return" is not allowed here.
Generally with lambdas, you don't explicitly return a value - the lambda returns the value of the last expression. Using your code as an example (it won't actually work but we'll get to that!):
handler.post {
...
companyNumber
}
which is the same as how things like map calls take a transformation function
listOf(1, 2, 3).map { it * 2 }
that's just doubling each number, but the result is being implicitly returned and stored in the resulting list, right? And it lets you chain lambdas together, since each one evaluates to a value (which might be Unit if it "doesn't return a result")
If you want, you can explicitly use what's called a qualified return:
handler.post {
...
return#post companyNumber
}
where you're naming the function call you're returning to.
Kotlin docs: returning a value from a lambda expression
Also if you want to return two values, you can't do that - so you'll have to bundle them up in a single object. You could just return a Pair, or create a data class that's more readable:
return#post Pair(companyName, companyNumber)
//or
data class CompanyDeets(val name: String, val number: String)
...
return#post CompanyDeets(companyName, companyNumber)
But aside from how you do it in general, why do you want to return anything here? Handler#post takes a Runnable which returns nothing (void in Java), and View.OnClickListener#onClick doesn't return anything either.
Neither of them would do anything with a value you returned - and if you explicitly return a value, that means your lambda's signature doesn't match (right now it's implicitly returning Unit to match what's expected by the caller), and you'll get an error
What you probably want to do instead, is create a function inside your class (Activity or whatever) that uses your data, something like fun doSomethingWith(companyName: String, companyNumber: String) and call that inside your lambda. That's way you're executing code in reaction to a click
just declare var Company Name in global, or create a function with that params
var companyName: String? = null
handler.post {
...
companyName = result.substringAfter("Name: ").substringBefore(";")
}
OR
handler.post {
...
save(result.substringAfter("Name: ").substringBefore(";"))
}
fun save(companyName: String){ ... }

Custom Components Sorting based on required Ordering

Trying to sort the custom components on defined order, and other subjects should be after that desired order: Like if there is any other subject except the defined List ie "GK" then it should be on last position etc.
However I am getting Null Pointer Exception due to subject is not defined in the requireList if scheduleCommandList have the subject which is not in requiredList. How can I overcome this?
Desired Order List is Below:
private val requiredList: HashMap<String, Int> = hashMapOf(
"Maths" to 0,
"Physics" to 1,
"Science" to 2,
)
Sorting function to sort the List:
private fun sortCommandList(scheduleCommandList: ArrayList<BaseComponent>): ArrayList<BaseComponent> {
val comparator = Comparator { o1: BaseComponent, o2: BaseComponent ->
return#Comparator requiredList[o1.name]!! - requiredList[o2.name]!!
}
val copy = arrayListOf<BaseComponent>().apply { addAll(scheduleCommandList) }
copy.sortWith(comparator)
return copy
}
It seems you understand the problem correctly. If an item is not present in requiredList then you still try to compare their required positions and this causes NullPointerException. Remember that you should use !! only in cases when you are sure there can't be a null. In this case null is possible and we have to handle it somehow. The easiest is to replace it with Int.MAX_VALUE which places the item at the end. Also, this code can be really much simpler:
private fun sortCommandList(scheduleCommandList: List<BaseComponent>): List<BaseComponent> {
return scheduleCommandList.sortedBy { requiredList[it.name] ?: Int.MAX_VALUE }
}
It can be even better to create this utility as extension function:
private fun List<BaseComponent>.mySort(): List<BaseComponent> {
return sortedBy { requiredList[it.name] ?: Int.MAX_VALUE }
}
Then we can simplify the name of the function, because it is implicit that it is used to sort BaseComponent objects.

How to update Recyclerview in real-time when a document is removed from firestore?

I want to update Recyclerview in realtime when a document is added or removed from firestore. I am using this logic in Kotlin:
for (doc in docs!!.documentChanges) {
val classElement: FireClassModel=doc.document.toObject(FireClassModel::class.java)
if (doc.type == DocumentChange.Type.ADDED) {
adapterList.add(classElement)
} else if(doc.type == DocumentChange.Type.REMOVED){
adapterList.remove(classElement)
}
adapter.notifyDataSetChanged()
}
Its working fine when document is added but it does not work when data is removed. It has no error but it doesn't update in real-time. It only updates when I restart the Application.
Updated
FireClassModel:
class FireClassModel {
var classID: String = ""
var className: String = ""
}
I tried this classesList.contains(classElement) and it returns false. It means I am unable to compare objects in my ArrayList.
Finally I have solved the issue. The issue was, I am not getting the right object.
I just replaced adapterList.remove(classElement) with following:
for (cls in adapterList) {
if (cls.classID == doc.document.id) {
adapterList.remove(cls)
}
}
Thanks to #Markus
Your code looks fine, but it seems that the element that you are trying to remove from the list cannot be found there.
adapterList.remove(classElement)
When removing an element from a list using remove(o: Object): Boolean the first matching element from the list will be removed. A matching element is an element where the equals method returns true.
listElement == elementToRemove // Kotlin
listElement.equals(elementToRemove); // Java
By default (that means if you do not override equals) objects will only be equal, if they share the same location in memory. In your example the element in the list sits at a different location than the element that you create from Firestore in your document change listener.
The solution depends on your FireClassModel. Looking at multiple FireClassModel objects, how would you decide which two of them are equal? Maybe they'll have the same id? Then override the equals method (and per contract also hashCode) and compare the fields that make two objects identical. For an id, the solution could look like that (generated by Android Studio):
class FireClassModel(val id: Int) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as FireClassModel
if (id != other.id) return false
return true
}
override fun hashCode(): Int {
return id
}
}
After that comparing two FireClassModel objects with the same ID will return true. Without overriding the equals method that would not be the case (unless you comparing an object to itself).
More about equals can be found on Stackoverflow and in the Java documentation.

Combine the latest values of a nested Observable on demand

I have some custom fields that each have a value that can be retrieved using a BehaviorSubject<Value>. What fields are shown are based on what I get from the API, so in the end I have n amount of BehaviorSubject<Value>s. I would like to group these values together into an Observable<List<Value>> where the list contains latest values from these fields (order is irrelevant). The problem however is that these fields are not all available at the same time because they're created while the UI loads so I cannot use Observable.combineLatest with the list of subjects.
What I currently have done is I have created the following variable:
private val values = BehaviorSubject.create<Pair<Int, Value>>()
I use this subject to subscribe to all of the field's subjects but map the subjects first with their position and make a pair of it.
fieldSubject.map {
Pair(position, value)
}.subscribe(values)
What I then want to do is group the values based on their position in the pair and get an Observable<List<Value>> where the list contains the latest values from each position. However I don't know how to proceed after grouping them using groupBy:
values.groupBy {
it.first
}
This results in an Observable<GroupedObservable<Pair, Value>>>. Finally this is how I think I should get to Observable<List<Value>>, but I don't know what to do from here.
groupBy doesn't seem helpful to me here.
Accumulating values is generally done using scan, here you can use a transformation like:
values.scanWith({ arrayOfNulls<Value>(N) }) { acc, (index, value) ->
acc.copyOf().apply {
set(index, value)
}
}
.map { it.filterNotNull() }
You could probably get away without copyOf(), but I think I encountered problems in the past when the accumulator function wasn't pure.
By the way, you can write position to value instead of Pair(position, value).
Also, you can use merge to get an Observable<Pair<Int, Value>> instead of creating a BehaviorSubject and subscribing it manually to all fields:
Observable.mergeArray(
fieldX.map { 0 to it },
fieldY.map { 1 to it },
fieldZ.map { 2 to it }
// ...
)
So overall, you could have a function that does it all for you:
inline fun <reified T : Any> accumulateLatest(vararg sources: Observable<out T>): Observable<List<T>> {
return Observable.merge(sources.mapIndexed { index, observable ->
observable.map { index to it }
})
.scanWith({ arrayOfNulls<T>(sources.size) }) { acc, (index, value) ->
acc.copyOf().apply {
set(index, value)
}
}
.map { it.filterNotNull() }
}
And then just call:
accumulateLatest(fieldX, fieldY, fieldZ)
.subscribe {
println("Latest list: $it")
}

kotlin Android sortwith Array issue

I use the below sortwith method to sort my ArrayList, I suppose it will sort the order number from small number to big number. Such as 10,9,8,7,6....0. But the result is not what I expected.Please kindly help to solve this issue.
companyList.add(companyReg)
companyList.sortedWith(compareBy { it.order })
for (obj in companyList) {
println("order number: "+obj.order)
}
Println result
See this example:
fun main(args: Array<String>) {
val xx = ArrayList<Int>()
xx.addAll(listOf(8, 3, 1, 4))
xx.sortedWith(compareBy { it })
// prints 8, 3, 1, 4
xx.forEach { println(it) }
println()
val sortedXx = xx.sortedWith(compareBy { it })
// prints sorted collection
sortedXx.forEach { println(it) }
}
Why this is works this way? Because in Kotlin most collections are immutable. And collection.sortedWith(...) is an extenstion function which returns sorted copy of your collection, but in fact you ignore this result.
Ofc you can use other methods modifying collections (like .sort()), or Collections.sort(collection, comparator). This way of sorting doesn't required to assign new collection (because there is no new collection, only current is modified).
Try this
companyList = companyList.sortedWith(compareBy { it.order })
You can check the document here https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/sorted-with.html

Categories

Resources