Change MutableList during Iteration - kotlin

Ok, so. I'm absolutely stumped by this problem.
I have a function, which sorts a list of objects and gives it back, à la
1, 1.1, 1.2, 1.2.1, 2, 2.2, 3.
fun getFolderHierachy(allFolders: List<FolderEntity>): List<FolderEntity>? {
//puts the entrypoints( 1, 2, 3) in a list
if (allFolders.isNotEmpty()) {
val foldersSorted: MutableList<FolderEntity> = mutableListOf()
allFolders.forEach {
if (it.parentDir == null) {
foldersSorted.add(it)
}
}
// should insert an element, after the matching one ( 1, 1.1, 2, 3)
while (foldersSorted.size != allFolders.size) {
foldersSorted.forEach { parentFolder ->
allFolders.forEach {
if (!foldersSorted.contains(it)) {
if (it.parentDir == parentFolder.uid) {
val index = foldersSorted.indexOf(parentFolder)
foldersSorted.add(index + 1, it)
}
}
}
}
}
println(foldersSorted)
return foldersSorted
}
return null
}
I know should work, because it already delievered me the correct results. However after the last update from my master, it throws this error:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: de.ur.mi.audidroid, PID: 16634
java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.next(ArrayList.java:860)
at de.ur.mi.audidroid.viewmodels.FolderViewModel.getFolderHierachy(FolderViewModel.kt:206)
at de.ur.mi.audidroid.viewmodels.FolderViewModel$initFolderSorting$1.onChanged(FolderViewModel.kt:45)
at de.ur.mi.audidroid.viewmodels.FolderViewModel$initFolderSorting$1.onChanged(FolderViewModel.kt:20)
at androidx.lifecycle.MediatorLiveData$Source.onChanged(MediatorLiveData.java:152)
at androidx.lifecycle.LiveData.considerNotify(LiveData.java:131)
at androidx.lifecycle.LiveData.dispatchingValue(LiveData.java:149)
at androidx.lifecycle.LiveData.setValue(LiveData.java:307)
at androidx.lifecycle.LiveData$1.run(LiveData.java:91)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
The update didn't even change anything relevant to this function. I know it hast something to do with the change of the list while iterating, but shouldn't MutableList take care of that?
I'm so confused at how I should change it to make it work again, please help.

When using foreach you use the List's iterator which doesn't allow concurrent modification until the loop exists. To get around this, use a standard for loop.
while (foldersSorted.size != allFolders.size) {
for (i in 0 until foldersSorted.size) {
val parentFolder = foldersSorted[i]
// Rest of code
}
}

Related

How to switch flow based on condition?

There is query and two filters. When set search filter and query is not empty, need to one flow. When set checked filter need to other flow.
According debug, onClickSearchFilter or onClickCheckedFilter calls with query or filter changed - return new flow. But in UI no changes, collector dont work second time.
How to switch flow based on condition?
When I debugging with breakpoints in flows, app crash every time A/libc: Fatal signal 11 (SIGSEGV), code 1, fault addr 0x14 in tid 23174 (JDWP), pid 23167 . Rebuild, clear cache, reload device - dont' help.
repeatOnStarted(viewModel.itemsFlow) {
// it doesn't work when flow is switched
pagingAdapter.submitData(it)
}
val itemsFlow = queryFlow
.debounce(1000)
.combine(filterFlow) { query, filter ->
when (filter) {
R.id.search_result_chip -> onClickSearchFilter(query)
R.id.checked_chip -> onClickCheckedFilter()
else -> throw Exception("")
}
}.flatMapMerge { // in order to switch from Flow<Flow<*>> to Flow<*>
it
}
private fun onClickSearchFilter(query: String): Flow<PagingData<ItemEntity>> {
return if (query.length < 2)
emptyFlow()
else Pager(BasePagingSource.getConfig()) {
SearchPagingSource(query, client)
}.flow.cachedIn(viewModelScope)
}
private fun onClickCheckedFilter(): Flow<PagingData<ItemEntity>> {
return Pager(
config = BasePagingSource.getConfig(),
remoteMediator = RemoteMediator(checkedIds, cacheDatabase, client)
) {
cacheDatabase.itemDao.getPagingSource(type, checkedIds)
}.flow
}

Test throw case in RxJava 3

I'm trying to test case when my rx chain should throw an exception but facing problem with it. When I'm trying to call assertError(error) it says that no error found. Here's example:
fun isAvailable(): Single<String> {
return mapper.getSymbol().map { symbol ->
if (repository.isAvailable(symbol)) {
symbol
} else {
throw Exception("Symbol is not available")
}
}
}
In test I mock repository to return false and after do something like:
val error = Exception("Symbol is not available")
whenever(mapper.getSymbol()).thenReturn(
Single.just(
symbol
)
)
whenever(repository.isAvailable(symbol)).thenReturn(false)
val test = symbolsRepository.isAvailable().test()
test.await()
.assertError(error)
But when I run test I see
Error not present (latch = 0, values = 0, errors = 1, completions = 0)
Caused by: java.lang.Exception: Symbol is not available
As #gpunto said, assertError does equals check on the Exception and those are not implemented by default beyond reference equality in standard Java.
You could use two other overloads of assertError to check for both the exception class and its message:
test.awaitDone()
.assertError(error.getClass())
.assertError({ it.getMessage().equals(error.getMessage()) })

Spring Reactor not working on handle operation while DB fetch

I'm using spring reactor. The code below is :
public Mono<ResponseEntity<SignUpResponse>> createSignUpForUser(SignUpRequest signUpRequest) {
return Mono.just(signUpRequest)
.map(sign -> {
Mono<UserDetailsEntity> userDetailsEntityMono = userDetailsRepository.findByPhoneNumber(sign.getMobileNumber());
userDetailsEntityMono.handle((user, sink) -> {
if (user != null) {
sink.error(new RuntimeException("Phone number already registered"));
}
});
return functionUserDetails.apply(sign);
})
.flatMap(userDetailsRepository::save)
.map(functionUser)
.flatMap(userRepository::save)
.map(usr -> ResponseEntity.ok(functionSignUpRes.apply(usr)))
.defaultIfEmpty(ResponseEntity.notFound().build())
.log();
}
Here the findByPhoneNumber(sign.getMobileNumber()) DB call is not working (the error is not throwing). The Rest of the operations are working and returning the response. Am i doing anything wrongly ? help me to fix this issue.
I think you need to rewrite your code a bit
public Mono<ResponseEntity<SignUpResponse>> createSignUpForUser(SignUpRequest signUpRequest) {
return userDetailsRepository.findByPhoneNumber(sign.getMobileNumber())
.flatMap(__ -> Mono.error(new RuntimeException("Phone number already registered")))
.switchIfEmpty(userDetailsRepository.save(functionUserDetails.apply(sign)))
.map(functionUser)
.flatMap(userRepository::save)
.map(usr -> ResponseEntity.ok(functionSignUpRes.apply(usr)))
.defaultIfEmpty(ResponseEntity.notFound().build())
.log()
;
}
Idea is that if you find that number then it should error, if result is empty then you would need to insert it so we need to use switch if empty

Kotlin nested for loops to asSequence

I'm trying to convert my nested for loop to asSequence in Kotlin. Here, my goal is to get and update the value of all my object array from another object array with the same key.
nested for loop:
val myFields = getMyFields()
val otherFields = getOtherFields()
for (myField in myFields) { // loop tru the my fields
for (otherField in otherFields) { // find the same fields
if (myField.key == otherField.key) { // if the same, update the value
val updatedMyField = myField.copy(value = otherValue.value)
myFields[myFields.indexOf(myField)] = updatedMyField // update my field value
break
}
}
}
What I've tried:
val updatedMyFields = getMyFields().asSequence()
.map { myField ->
getOtherFields().asSequence()
.map { otherField ->
if (myField.key == otherField.key) {
return#map otherField.value
} else {
return#map ""
}
}
.filter { it?.isNotEmpty() == true }
.first()?.map { myField.copy(value = it.toString()) }
}
.toList()
but this does not compile as it will return List<List<MyField>>.
I'm just looking for something much cleaner for this.
As comments suggest, this would probably be much more efficient with a Map.
(More precisely, a map solution would take time proportional to the sum of the list lengths, while the nested for loop takes time proportional to their product — which gets bigger much faster.)
Here's one way of doing that:
val otherFields = getOtherFields().associate{ it.key to it.value }
val myFields = getMyFields().map {
val otherValue = otherFields[it.key]
if (otherValue != null) it.copy(value = otherValue) else it
}
The first line creates a Map from the ‘other fields’ keys to their values.  The rest then uses it to create a new list from ‘my fields’, substituting the values from the ‘other fields’ where present.
I've had to make assumptions about the types &c, since the code in the question is incomplete, but this should do the same.  Obviously, you can change how it merges the values by amending the it.copy().
There are likely to be even simpler and more efficient ways, depending on the surrounding code.  If you expanded it into a Minimal, Complete, and Verifiable Example — in particular, one that illustrates how you already use a Map, as per your comment — we might be able to suggest something better.
Why do you want to use asSequence() ? You can go for something like that:
val myFields = getMyFields()
val otherFields = getOtherFields()
myFields.forEach{firstField ->
otherFields.forEach{secondField ->
if (firstField.key == secondField.key) {
myFields[myFields.indexOf(firstField)] = secondField.value
}
}
}
This will do the same job than your nested for loop and it's easier to read, to understand and so to maintain than your nested asSequence().

Kotlin: function to stop iteration when predicate satisfied

I'm looking for a function in Kotlin which stops an iteration once a predicate is fulfilled:
val services = listOf(service1, service2)
...
var res: Result = null
services.stopIfPredicateFulFilled { service ->
res = service.doSomething()
res != null
}
While this example is not really nice since res is overwritten in each iteration I hope the intention is clear.
forEach doesn't do the job the way I expect it to be done. So, I was wondering if there isn't anything else.
You can use the functions find { ... } and firstOrNull { ... } (they are equivalent, just named differently). They find the first element satisfying the predicate and return that element, ignoring all the remaining elements.
services.find { service ->
res = service.doSomething()
res != null
}