RxJava: Issue with withLatestFrom operator when using in combination with .subScribeOn - kotlin

Please find the below kotlin code snippet:
val dummyApi: PublishSubject<String> = PublishSubject.create()
fun withLatestFromtest() {
dummyApi.onNext("1")
getOtherObservable().withLatestFrom(dummyApi)
.subscribeOn(Schedulers.io())
.subscribeBy(
onNext = {
Log.e("TAG", "Pair ${it.first} /// ${it.second}")
},
onError = {
it.printStackTrace()
},
onComplete = {
Log.e("TAG", "Pair2 Complete")
}
)
dummyApi.onNext("2")
dummyApi.onNext("3")
}
private fun getOtherObservable(): Observable<String>{
return Observable.just("API")
.delay(2, TimeUnit.SECONDS)
}
Expected Output -
Pair API /// 3
Pair2 Complete
Actual Output -
Pair2 Complete
When I removed .subscribeOn(Schedulers.io()) it worked as expected. I am not able to understand why scheduler is creating the issue.

Related

What is the best way to get data from an API using retrofit when something needs to wait on that data?

I have a retrofit API call but am having trouble getting the data out of it:
This is in a class file that's not a viewModel or Fragment. It's called from the apps main activity view model. I need to be able to get the data from the API and wait for some processing to be done on it before returning the value back the view model. Newer to kotlin and struggling with all the watchers and async functions. The result of this an empty string is the app crashes, because it's trying to access data before it has a value.
From class getData which is not a fragment
private lateinit var data: Data
fun sync(refresh: Boolean = false): List<String> {
var info = emptyList<String>
try {
getData(::processData, ::onFailure)
info = data.info
} catch(e: Throwable){
throw Exception("failed to get data")
}
}
}
return info
}
fun getData(
onSuccess: KFunction1<ApiResponse>?, Unit>,
onFailed: KFunction1<Throwable, Unit>
) {
val client = ApiClient().create(Service.RequestData)
val request = client.getData()
request.enqueue(object : Callback<ApiResponse> {
override fun onResponse(
call: Call<ApiResponse>,
response: Response<ApiResponse>
) {
onSuccess(response.body())
}
override fun onFailure(call: Call<RegistryResponse<GlobalLanguagePack>>, t: Throwable) {
onFailed(Exception("failed to get data"))
}
})
}
private fun processData(body: ApiResponse?) {
requireNotNull(body)
data = body.data
}
```
From appViewModel.kt:
```
fun setUpStuff(context: Context, resources: AppResources) = viewModelScope.launch {
val stuff = try {
getData.sync()
} catch (e: Exception) {
return#launch
}
if (stuff.isEmpty()) return#launch
}
```

MockK - cannot mock same function twice

I am trying to test the getTopicNames function (below) in two scenarios: If it succeeds and if it does not succeed.
fun getTopicNames(): Either<Exception, Set<String>> =
try {
adminClient.listTopics()
.names()
.get()
.right()
} catch (exception: ExecutionException) {
exception.left()
}
This is the test class in which I am doing those two scenarios. If I run each test individually, they both suceed. If I run the entire class the second to execute fails because for some reason the previous mock on adminClient.listTopics() is being retained.
These are the versions for everything involved:
kotlin: 1.3.72
koin: 2.1.6
junit: 5.6.1
mockk: 1.10.0
class TopicOperationsTest {
#BeforeEach
fun start() {
val testModule = module(createdAtStart = true) {
single { mockk<AdminClient>() }
}
startKoin { modules(testModule) }
}
#AfterEach
fun stop() {
stopKoin()
}
#Test
fun `getTopicNames() returns a Right with the topics names`() {
val adminClient = get(AdminClient::class.java)
val listOfTopicsToReturn = mockk<ListTopicsResult>()
val expectedTopics = setOf("Topic1", "Topic2", "Topic3")
every { adminClient.listTopics() } returns listOfTopicsToReturn
every { listOfTopicsToReturn.names() } returns KafkaFuture.completedFuture(expectedTopics)
println("listOfTopicsToReturn.names(): " + listOfTopicsToReturn.names())
println("adminClient.listTopics(): " + adminClient.listTopics())
println("getTopicNames(): " + getTopicNames())
assertThat(getTopicNames().getOrElse { emptySet() }, `is`(expectedTopics))
}
#Test
fun `getTopicNames() returns a Left if failing to get topic names`() {
val adminClient = get(AdminClient::class.java)
every { adminClient.listTopics() } throws ExecutionException("Some Failure", Exception())
assertThat(getTopicNames(), IsInstanceOf(Either.Left::class.java))
}
}
This is the error I get, caused by the fact that the test that verifies the failure is the first to run:
java.lang.AssertionError:
Expected: is <[Topic1, Topic2, Topic3]>
but: was <[]>
Expected :is <[Topic1, Topic2, Topic3]>
Actual :<[]>
<Click to see difference>
Already tried clearAllMocks() on the BeforeEach method but it does not solve my problem as I just start getting:
io.mockk.MockKException: no answer found for: AdminClient(#1).listTopics()
I found a solution that makes everything work. It is a combination of:
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
Having the mock as a class object
MockKAnnotations.init(this) in the #BeforeEach method
clearMocks() specifying the actual mock to be cleared (should work for multiple mocks too, just separated by commas.
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TopicOperationsTest {
private var adminClientMock = mockk<AdminClient>()
#BeforeEach
fun start() {
MockKAnnotations.init(this)
val testModule = module(createdAtStart = true) {
single { adminClientMock }
}
startKoin { modules(testModule) }
}
#AfterEach
fun stop() {
clearMocks(adminClientMock)
stopKoin()
}
#Test
fun `getTopicNames() returns a Right with the topics names`() {
val adminClient = get(AdminClient::class.java)
val listOfTopicsToReturn = mockk<ListTopicsResult>()
val expectedTopics = setOf("Topic1", "Topic2", "Topic3")
every { adminClient.listTopics() } returns listOfTopicsToReturn
every { listOfTopicsToReturn.names() } returns KafkaFuture.completedFuture(expectedTopics)
assertThat(getTopicNames().getOrElse { emptySet() }, `is`(expectedTopics))
}
#Test
fun `getTopicNames() returns a Left if failing to get topic names`() {
val adminClient = get(AdminClient::class.java)
every { adminClient.listTopics() } throws ExecutionException("Some Failure", Exception())
assertThat(getTopicNames(), IsInstanceOf(Either.Left::class.java))
}
}

What is a clean way to wait for a response?

I am sending a message(custom protocol, no HTTP) to my server and want to wait for a response. It is working with the following code:
class Connection {
val messages: Observable<Message>
fun sendMessageWithAnswer(message: Message, timeout:Int = 10): Observable<Answer> {
if (!isConnected) {
return Observable.just(Answer.NoConnection)
}
val result = BehaviorSubject.create<Answer>()
val neverDisposed = messages.filter {
it.header.messageId == message.header.messageId
}
.map { Answer.Success(it) as Answer}
.mergeWith(Observable.timer(timeout.toLong(), TimeUnit.SECONDS)
.map { Answer.Timeout })
.take(1).singleOrError()
.subscribe(
{result.onNext(it)},
{
// Should never happen
throw IllegalStateException("Waiting for answer failed: $it")
}
)
sendMessage(message)
return result
}
}
The problem with this solution that "neverDisposed" gets never disposed, is this a memory leak?
My other solutions are not working for this test case:
#Test
fun ImmediateAnswer() {
prepare()
val message = ...
val answerObservable = connection.sendMessageWithAnswer(message, timeout = 1)
connection.receiveMessage(message)
val answer = answerObservable.test()
answer.awaitCount(1)
Thread.sleep(1000)
Assert.assertEquals(1, answer.valueCount())
Assert.assertEquals(Answer.Success(message), answer.values()[0])
}
Do you have a cleaner solution for this problem?

RxJava : How to maintain Observable alive even after getting error in onError() or ReSubscribe the same Observable

Actually I have created a RxSearch type configuration. In which I have attached an Edittext textChangeListener with the PublishSubject. Using the events to send the characters to the Observable which is being used as input for the retrofit API call.
Problem
Only issue I m facing is sometime I got the error from API "unexpected end of stream" inside onError() callback of observable. Once I got the error, Observable stops working.
Observable shuts down, not able to get the characters from PublishSubject's onNext().
Look at RxSearchObservable
class RxSearchObservable {
companion object {
fun fromView(editText: EditText): Observable<String> {
val subject = PublishSubject.create<String>()
editText.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
//subject.onComplete()
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
//subject.onNext(s.toString())
}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
if (s.isNotEmpty()) subject.onNext(s.toString())
}
})
return subject
}
}
}
How I subscribing and Making an Retrofit API call in side SwitchMap.
RxSearchObservable.fromView(edtToolSearch)
.debounce(700, TimeUnit.MILLISECONDS)
.distinctUntilChanged()
.retryWhen { t -> t.delay(3, TimeUnit.SECONDS) }
.switchMap { searchTerm ->
runOnUiThread { progressBar.visibility = View.VISIBLE }
apiManager.getSearchUnits(searchTerm)
}
.onErrorResumeNext(Observable.empty())
.subscribe({ response ->
Log.i("Called subscribe", ":::::::::::+++++++++++++++ GONE")
progressBar.visibility = View.GONE
if (response.isSuccessful) {
val units = response.body()
val searchedDatasets = units?.dataset
if (searchedDatasets?.size!! > 0) {
val searchAdapter = SearchAdapter(this#MapActivity, searchedDatasets, false)
listSearch.visibility = View.VISIBLE
listSearch.adapter = searchAdapter
} else {
toast("No items found !!!")
}
} else {
apiError = ErrorUtils.parseError(response)
toast(apiError.msg)
}
}, { t: Throwable? ->
progressBar.visibility = View.GONE
toast(t?.message.toString())
}))
Any Idea, Help, Suggestion will be appreciated. Thanks in advance.
A stream which errors is terminated. You can retry() the subscription, but this should be done conditionally only. Maybe with timeout, maybe only a few times, maybe on certain errors only.
In your case you should consider handling the error of the API call within the switchMap. Like this the error doesn't reach the main stream.
.switchMap { searchTerm ->
runOnUiThread { progressBar.visibility = View.VISIBLE }
apiManager.getSearchUnits(searchTerm)
.onErrorResumeNext(Observable.empty())
}

RxJava + Retrofit Unit Test Kotlin Always Failed

I tried to create Unit Test using Rxjava + Retrofit but it always give an error.
I have tried all tutorials and reference related of my questions. I did success when create an unit test of other method (other case), but failed in this case (Rx + retrofit).
Request Data Code:
fun getDetailEvent(idEvent: String?) {
view.showLoading()
apiService.getDetailEvent(idEvent)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe {
val compositeDisposable: CompositeDisposable? = null
compositeDisposable?.add(it)
}
.doFinally { view.hideLoading() }
.subscribe({
val listModel = it
if (listModel != null) {
view.onDetailEventLoaded(listModel)
} else {
view.onDetailEventLoadFailed("Empty or Error List")
}
},
{
val errorMessage = it.message
if (errorMessage != null) {
view.onDetailEventLoadFailed(errorMessage)
}
})
}
Unit Test Code :
class DetailNextMatchPresenterTest {
#Mock
private lateinit var view : DetailNextMatchView
#Mock
private lateinit var apiService: ApiService
private lateinit var presenter: DetailNextMatchPresenter
#Before
fun setup(){
MockitoAnnotations.initMocks(this)
presenter = DetailNextMatchPresenter(view, apiService)
}
#Test
fun getDetailEvent() {
val event : MutableList<EventModel> = mutableListOf()
val response = ResponseEventModel(event)
val idEvent = "44163"
`when`(apiService.getDetailEvent(idEvent)
.test()
.assertSubscribed()
.assertValue(response)
.assertComplete()
.assertNoErrors()
)
presenter.getDetailEvent(idEvent)
verify(view).showLoading()
verify(view).onDetailEventLoaded(response)
verify(view).hideLoading()
}
}
I appreciate all suggestion. Thanks
I believe that the issue is that you haven't forced your code to behave synchronously in the context of your test, so the Observable runs in parallel to your test. Try adding this in your setup method:
RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() } If you're using RxJava2. Try looking for a similar method if you're using RxJava 1.