Timeout test does not return TimeoutException - rxjava - kotlin

The test fails as there is no exception thrown. It simply completes instead of timing out.
#Test
fun timeout() {
val testScheduler = TestScheduler()
val sub = Observable.just(true)
.filter{ it -> !it }
.timeout(10, TimeUnit.SECONDS, testScheduler)
val testSubscriber = sub.subscribeOn(testScheduler).test()
testScheduler.advanceTimeBy(20, TimeUnit.SECONDS)
testSubscriber.assertError(TimeoutException::class.java)
}
I've been at this block for over an hour and I just don't see why it's failing. It's probably something super obvious, but I feel I need another set of eyes to point it out for me.

Here's a test that gets the expected result:
#Test
fun timeout() {
val testScheduler = TestScheduler()
val sub = Observable.just(true) // 1
.delaySubscription(Observable.never<Boolean>()) // 2
.timeout(10, TimeUnit.SECONDS, testScheduler) // 3
val testSubscriber = sub.subscribeOn(testScheduler).test()
testScheduler.advanceTimeBy(20, TimeUnit.SECONDS)
testSubscriber.assertError(TimeoutException::class.java)
Here's an explanation of what's going on:
We just need a Observable to start with, the filter you had before was just making this be an empty one
We want to delay the subscription with something that won't finish, so one alternative is an Observable than never emits
From here on, it's the same you had

Related

Flow<T>.distinctUntilChanged() is not working

I'm trying to have a coroutine run while a SharedFlow is active (subscriptionCount is greater than zero) and be cancelled when the count drops. But somehow even something as simple as distinctUntilChanged() is not working as it should and I'm baffled.
For this I'm making a "onActive" extension like this:
fun <T : Any> MutableSharedFlow<T>.onActive(
block: suspend CoroutineScope.() -> Unit
): Flow<T> {
val original = this
val isActiveFlow: Flow<Boolean> = subscriptionCount
.map {
println("Class: Count is $it")
it > 0
}
.distinctUntilChanged()
return isActiveFlow.flatMapLatest { isActive ->
println("Class: isActive is $isActive")
// here would be the code that calls `block`
// but just this exactly as is, already triggers the error
original // still emits the original flow,
// that is needed or else subscriptionCount never changes
}
}
This initially this seems to work, but running a test on it that adds several subscribers, will print "isActive is true" several times in a row. Why is distinctUntilChanged() not working? This repeated call messes-up with the rest of the logic in the redacted area.
The test is like this:
#Test
fun `onActive is called only once with multiple subscribers`() = runBlocking {
val flow = MutableSharedFlow<Int>(
replay = 2,
onBufferOverflow = BufferOverflow.DROP_OLDEST
).apply {
repeat(5) { tryEmit(it) }
}.onActive {
}
val jobs = mutableListOf<Job>()
repeat(3) { count ->
jobs.add(flow.onEach {
println("Test: Listener $count received $it")
}.launchIn(this))
}
delay(100)
jobs.forEach { it.cancel() }
jobs.forEach { it.join() }
}
running this the output is:
Class: Count is 0
Class: isActive is false
Class: Count is 1
Class: Count is 1
Class: isActive is true
Class: Count is 2
Class: Count is 2
Class: isActive is true
Class: Count is 3
Test: Listener 0 received 3
Test: Listener 0 received 4
Test: Listener 1 received 3
Test: Listener 1 received 4
Test: Listener 2 received 3
Test: Listener 2 received 4
Class: Count is 2
Class: isActive is true
Class: Count is 3
Class: Count is 3
Class: Count is 3
Test: Listener 0 received 3
Test: Listener 0 received 4
So the question, why is distinctUntilChanged() not working and how can I fix it?
It seems the behaviour you're seeing is actually correct as far as distinctUntilChanged is concerned:
the first registered subscriber collects the original 2 replayed elements with the starting isActive=false value
then isActive becomes true because of that first susbcription, so that first subscriber recollects the original flow due to flatMapLatest, and thus gets again the replayed elements
the other 2 subscribers arrive when the subscriptionCount is already non-0 so isActive stays true for them until they are cancelled
If the coroutine you launch "while there are subscribers" is meant to produce elements in the SharedFlow, I would rather define the flow like a channelFlow/callbackFlow initially, and then use shareIn with SharingStarted.WhileSubscribed to have this "run when there are susbcribers" behaviour.
If it's "just on the side", you probably want an external scope and just launch a coroutine separately to listen to sharedFlow.subscribersCount and start/stop the "sidecar" coroutine.

Efficient way to update progress of long task while processing a task with Mono&Flux

I tried to make a method for long task which sends events of progress.
The method implemented by using Flux and Mono.
I figured out two ways to do it.
But I can't decide which approach is more efficient and elegant, or the other one.
Way 1.
var progressEvents = Sinks.Many<Progress> = Sinks.many().multicast()
fun doLongTask(taskFile : Mono<File>) : Mono<Another> {
return taskFile.map{ file ->
progressEvents.tryEmit(Progress("1"))
doSomething(file)
}.map{ something ->
progressEvents.tryEmit(Progress("2"))
doAnother(something)
}
}
Way2.
var progressEvents = Sinks.Many<Progress> = Sinks.many().multicast()
fun doLongTask(taskFile : Mono<File>) : Mono<Another> {
var doSomeMap = taskFile.map{ file ->
doSomething(file)
}.share()
doSomeMap.map{ something -> Progress("1")}.subscribe{progressEvents::tryEmit}
var doAnotherMap = doSomeMap.map{ something ->
doAnother(something)
}.share()
doAnotherMap.map{ another -> Progress("2")}.subscribe{progressEvents::tryEmit}
return doAnotherMap
}

How do I pull entries from an ArrayList at random in Kotlin?

This is my first Kotlin project. I am learning as I go and I have reached a roadblock.
I have an ArrayList of questions that I want to pull into that app in a random order. I've tried assigning the .random to the point where the question is assigned (right now it is set to CurrentPosition-1) but that only randomized the question and didn't pull the correct answers along with the questions.
How do I either bundle the answers to the question or is there a better way to get the questions to shuffle in order? I plan on having 50+ questions but only 10 will show each time the test is taken. I don't want the same 10 questions showing each time the user opens the test.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_quiz_questions)
mQuestionsList=Constants.getQuestions()
setQuestion()
}
private fun setQuestion(){
val question = mQuestionsList!![mCurrentPosition-1]
defaultOptionsView()
if(mCurrentPosition == mQuestionsList!!.size){
submitBtn.text = "Finish"
}else{
submitBtn.text = "Submit"
}
progressBar.progress = mCurrentPosition
tv_progress.text = "$mCurrentPosition" + "/" + progressBar.max
tv_question.text = question!!.question
test_image.setImageResource(question.image)
tvOptionOne.text = question.optionOne
tvOptionTwo.text = question.optionTwo
tvOptionThree.text = question.optionThree
tvOptionFour.text = question.optionFour
}
private fun defaultOptionsView(){
val options = ArrayList<TextView>()
options.add(0, tvOptionOne)
options.add(1, tvOptionTwo)
options.add(2, tvOptionThree)
options.add(3, tvOptionFour)
Here is my Array
object Constants{
const val TOTAL_QUESTIONS: String = "total_questions"
const val CORRECT_ANSWERS: String = "correct_answers"
fun getQuestions(): ArrayList<Question>{
val questionsList = ArrayList<Question>()
val q1 = Question(
R.drawable.questionmark,
1,
"Who is Regional Manager of\n Dunder Mifflin Scranton?",
"Michael",
"Jim",
"Pam",
"Dwight",
1,
)
I appreciate any help at all. Thank you in advance.
list.shuffled().take(10) And make your mQuestionsList property type List instead of ArrayList since you don’t need to modify it after retrieval. You should also probably make it lateinit or initialize it at its declaration site so you won’t have to make the type nullable and have to resort to !!, which is generally a code smell. So I would declare it as var mQuestionsList: List<Question> = emptyList() and whenever you want new values do mQuestionsList = Constants.getQuestions().shuffled().take(10).

Difference between using getOrDefault() and elvis operator on a Map

Could someone explain why these two lines act differently? activeGames is a MutableMap<String, Game>.
val game = activeGames.getOrDefault(id, Game(id))
val game = activeGames[id]?:Game(id)
For whatever reason, whenever I pass in the id key for a game that I know exists, calling getOrDefault always does the default behavior of creating a new game instead of grabbing the game from the map. But the line with the elvis operator works like I expect, and gets the existing game from the map.
I am instantiating activeGames with:
val activeGames = mutableMapOf<String, Game>()
I think you have a problem with the id you pass to getOrDefault
try debugging your code and throw an exception if activeGames.containsKey(id)
returns false.
if I reproduce your logic that you describe this what we expect to get
and I just test it and this is the outcome.
data class Game(val gameId: String)
fun main(args: Array<String>) {
val id = "bla"
val activeGames = mapOf(id to Game(id))
val game1 = activeGames.getOrDefault(id, Game(id))
val game2 = activeGames[id] ?: Game(id)
println("game1 : $game1")
println("game2 : $game2")
}
output:
game1 : Game(gameId=bla)
game2 : Game(gameId=bla)

How to typesafe reduce a Collection of Either to only Right

Maybe a stupid question but I just don't get it.
I have a Set<Either<Failure, Success>> and want to output a Set<Success> with Arrow-kt.
You can map the set like this for right:
val successes = originalSet.mapNotNull { it.orNull() }.toSet()
or if you want the lefts:
val failures = originalSet.mapNotNull { it.swap().orNull() }.toSet()
The final toSet() is optional if you want to keep it as a Set as mapNotNull is an extension function on Iterable and always returns a List
PS: No stupid questions :)
Update:
It can be done avoiding nullables:
val successes = originalSet
.map { it.toOption() }
.filter { it is Some }
.toSet()
We could potentially add Iterable<Option<A>>.filterSome and Iterable<Either<A, B>.mapAsOptions functions.
Update 2:
That last example returns a Set<Option<Success>>. If you want to unwrap the results without using null then one thing you can try is to fold the Set:
val successes = originalSet
.fold(emptySet<Success>()) { acc, item ->
item.fold({ acc }, { acc + it })
}
This last option (unintended pun) doesn't require the use of Option.