Using emit to build a Kotlin flow runs indefinitely and doesnt complete - kotlin

I use a java library with which I can subscribe to events from my eventstore db.
I can create a subscription according to the following SubscirptionListener
public abstract class SubscriptionListener {
public void onEvent(Subscription subscription, ResolvedEvent event) {
}
public void onError(Subscription subscription, Throwable throwable) {
}
public void onCancelled(Subscription subscription) {
}
}
I would like to emit ResolvedEvents as part of a flow each time the subscription is triggered. However, the call to emit doesn't finish.
fun flowSubscriptionListener(
streamName: String,
options: SubscribeToStreamOptions = SubscribeToStreamOptions.get(),
onError: (subscription: Subscription?, throwable: Throwable) -> Unit = { _, _ -> },
onCancelled: (subscription: Subscription) -> Unit = { _ -> }
): Flow<ResolvedEvent> {
return flow {
val listener = object : SubscriptionListener() {
override fun onEvent(subscription: Subscription, event: ResolvedEvent) {
logger.info {
"Received event ${event.originalEvent.streamRevision}#${event.originalEvent.streamId}"
}
runBlocking {
logger.info { "emitting event" }
this#flow.emit(event)
logger.info { "Event emitted" }
}
}
override fun onError(subscription: Subscription?, throwable: Throwable) {
logger.error {
"Received error with message: ${throwable.message ?: "No message"} on subscription ${subscription?.subscriptionId}"
}
onError(subscription, throwable)
}
override fun onCancelled(subscription: Subscription) {
logger.debug { "Subscription ${subscription.subscriptionId} cancelled" }
onCancelled(subscription)
}
}
client.subscribeToStream(streamName, listener).await()
}.buffer(10)
}
I have a sample setup where I await a flow with three events
flowSubscriptionListener(
streamName = "SampleTournament-adb517b8-62e9-4305-b3b6-c1e7193a6d19",
).map {
it.event.eventType
}.collect {
println(it)
}
However, I receive no events at all. The console output shows me that invocation of emit never terminates.
[grpc-default-executor-1] INFO lib.eventstoredb.wrapper.EskWrapperEsdb - Received event 0#SampleTournament-adb517b8-62e9-4305-b3b6-c1e7193a6d19
[grpc-default-executor-1] INFO lib.eventstoredb.wrapper.EskWrapperEsdb - emitting event
I am expecting the logging of "Event emitted"

In order to wrap callback-based API, you should use callbackFlow instead. It supports concurrent emissions, which I think is likely your problem here.
Also, it will properly handle the cancellation of the subscription when the flow itself is cancelled (via awaitClose()).
Here is one way to do it:
fun EventStoreDBClient.flowSubscription(
streamName: String,
options: SubscribeToStreamOptions = SubscribeToStreamOptions.get(),
): Flow<ResolvedEvent> = callbackFlow {
val listener = object : SubscriptionListener() {
override fun onEvent(subscription: Subscription, event: ResolvedEvent) {
logger.info { "Received event ${event.originalEvent.streamRevision}#${event.originalEvent.streamId}" }
logger.info { "Emitting event" }
trySendBlocking(event)
logger.info { "Event emitted" }
}
override fun onError(subscription: Subscription?, throwable: Throwable) {
logger.error {
"Received error with message: ${throwable.message ?: "No message"} on subscription ${subscription?.subscriptionId}"
}
close(throwable)
}
override fun onCancelled(subscription: Subscription) {
logger.debug { "Subscription ${subscription.subscriptionId} cancelled" }
close()
}
}
val subscription = subscribeToStream(streamName, listener, options).await()
awaitClose {
subscription.stop()
}
}.buffer(10)
Note that I also converted it to an extension function on EventStoreDBClient, which seems appropriate here. And I removed the error/cancellation callbacks because Flow already handles those (you can put them back if you need them)

Related

How to create a polling mechanism with kotlin coroutines?

I am trying to create a polling mechanism with kotlin coroutines using sharedFlow and want to stop when there are no subscribers and active when there is at least one subscriber. My question is, is sharedFlow the right choice in this scenario or should I use channel. I tried using channelFlow but I am unaware how to close the channel (not cancel the job) outside the block body. Can someone help? Here's the snippet.
fun poll(id: String) = channelFlow {
while (!isClosedForSend) {
try {
send(repository.getDetails(id))
delay(MIN_REFRESH_TIME_MS)
} catch (throwable: Throwable) {
Timber.e("error -> ${throwable.message}")
}
invokeOnClose { Timber.e("channel flow closed.") }
}
}
You can use SharedFlow which emits values in a broadcast fashion (won't emit new value until the previous one is consumed by all the collectors).
val sharedFlow = MutableSharedFlow<String>()
val scope = CoroutineScope(Job() + Dispatchers.IO)
var producer: Job()
scope.launch {
val producer = launch() {
sharedFlow.emit(...)
}
sharedFlow.subscriptionCount
.map {count -> count > 0}
.distinctUntilChanged()
.collect { isActive -> if (isActive) stopProducing() else startProducing()
}
fun CoroutineScope.startProducing() {
producer = launch() {
sharedFlow.emit(...)
}
}
fun stopProducing() {
producer.cancel()
}
First of all, when you call channelFlow(block), there is no need to close the channel manually. The channel will be closed automatically after the execution of block is done.
I think the "produce" coroutine builder function may be what you need. But unfortunately, it's still an experimental api.
fun poll(id: String) = someScope.produce {
invokeOnClose { Timber.e("channel flow closed.") }
while (true) {
try {
send(repository.getDetails(id))
// delay(MIN_REFRESH_TIME_MS) //no need
} catch (throwable: Throwable) {
Timber.e("error -> ${throwable.message}")
}
}
}
fun main() = runBlocking {
val channel = poll("hello")
channel.receive()
channel.cancel()
}
The produce function will suspended when you don't call the returned channel's receive() method, so there is no need to delay.
UPDATE: Use broadcast for sharing values across multiple ReceiveChannel.
fun poll(id: String) = someScope.broadcast {
invokeOnClose { Timber.e("channel flow closed.") }
while (true) {
try {
send(repository.getDetails(id))
// delay(MIN_REFRESH_TIME_MS) //no need
} catch (throwable: Throwable) {
Timber.e("error -> ${throwable.message}")
}
}
}
fun main() = runBlocking {
val broadcast = poll("hello")
val channel1 = broadcast.openSubscription()
val channel2 = broadcast.openSubscription()
channel1.receive()
channel2.receive()
broadcast.cancel()
}

Coroutine never calls API after job either cancelled or pending

How do I make calls when I click loan1 button happen after getting a JobCancellationException on other calls ?
class MainRepository{
//called many times
suspend fun getLoanOptions(): Resource<LoanOptionsResponse> {
return try {
val response = apiService.getLoanOptions("id")
responseHandler.handleSuccess(response)
} catch (e: Exception) {
responseHandler.handleException(e)
}
}
}
class MainViewModel : ViewModel(), CoroutineScope {
private val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
Timber.e("$throwable")
}
override val coroutineContext: CoroutineContext
get() = Dispatchers.IO + SupervisorJob() + exceptionHandler
private val mainRepo: MainRepository by lazy { MainRepository() }
//extra calls to this fails
fun getLoanOptions(): LiveData<Resource<LoanOptionsResponse>> {
return liveData(coroutineContext) {
val data = mainRepo.getLoanOptions()
emit(Resource.loading(null))
emit(data)
}
}
override fun onCleared() {
super.onCleared()
coroutineContext.cancel()
}
}
//in mainactivity I call it
class MainActivity : BaseActivity() {
val vm: MainViewModel by lazy { ViewModelProvider(this).get(MainViewModel::class.java) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
loan1.setOnClickListener {
// if any previous api has 403 this one does not work any more ?
vm.getLoanOptions().observe(this, Observer {
//data
}
}
loan2.setOnClickListener {
}
}
}
I clicked on button -> Api is called -> got success response
Again I clicked on -> Api is called -> got success response
I get 403 in other API call
I clicked on button -> Api is not called
No other Api calls gets called in this Activity :(
I get this after few minutes
kotlinx.coroutines.JobCancellationException: Job was cancelled; job=StandaloneCoroutine

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())
}

Kotlin coroutine immediately give an exception if last operation finished with exception

When I was try to login on my service via retrofit. When my service is off, 10 seconds after clicking the button I got an SocketTimeoutException exception.
So far everything is normal but again, I clicked the button again after the error gave the same error immediately. What's wrong?
interface LoginService {
#FormUrlEncoded
#POST("/login")
fun login(#Field("id") id: String, #Field("pw") pw: String): Deferred<Response<User>>
}
class LoginViewModel : ViewModel() {
private var job: Job = Job()
private val scope: CoroutineScope = CoroutineScope(Dispatchers.Main + job)
private val service by lazy { RetrofitApiFactory().create(LoginService::class.java) }
private val excHandler = CoroutineExceptionHandler { _, throwable ->
Timber.e(throwable);
}
fun doLogin(id: String, pw: String) {
scope.launch(excHandler) {
val response = service.login(id, pw).await()
if (response.isSuccessful) {
response.body()
?.let { user -> doOnSuccess(user) }
?: doOnError(InvalidUserException())
} else doOnError(Exception())
}
}
private fun CoroutineScope.doOnError(e: Throwable) {
excHandler.handleException(coroutineContext, e)
}
private fun doOnSuccess(user: User) {
...
}
override fun onCleared() {
job.cancel()
}
}
You need to change your CoroutineScope to not re-use the same Job. It's already considered as failed, so it will not even begin executing.
See related issue on github.

kotlin, got “Type mismatch. Required: Disposable? Found: Unit” when using observer object instance in the subscribe()

Edit:
based on Dmitry Ikryanov's suggestion,
using DisposableObserver will compile, but it causes crash
io.reactivex.exceptions.ProtocolViolationException: It is not allowed to
subscribe with a(n) com.DataManager$theObserver$1 multiple times. Please
create a fresh instance of com.DataManager$theObserver$1 and subscribe that
to the target source instead.
the only code of subecribWith(), which has been called only once
fun initSession() {
if (mDisposable != null && mDisposable!!.isDisposed) {
mDisposable!!.dispose()
}
mDisposable = RxBus.listen(DataEvent::class.java).subscribeWith(theObserver) <=== crash at here
}
the DisposableObserver is a member variable of the class:
var theObserver: DisposableObserver<DataEvent> = object : DisposableObserver<DataEvent>() {
override fun onComplete() {
Log.e(TAG, "onComplete: All Done!") }
override fun onNext(t: DataEvent) {
Log.e(TAG, "Next: " + t)
onDataReady(t) }
override fun onError(e: Throwable) {
Log.e(TAG, "onError: ")
}
}
===
Original question:
trying to use RxJava subscribe() in kotlin, get an error “Type mismatch. Required: Disposable? Found: Unit”, not sure what it means, anyone knows?
class DataEvent {}
using RxBus
object RxBus {
private val publisher = PublishSubject.create<Any>()
fun publish(event: Any) {
publisher.onNext(event)
}
// Listen should return an Observable and not the publisher
// Using ofType we filter only events that match that class type
fun <T> listen(eventType: Class<T>): Observable<T> = publisher.ofType(eventType)
}
when call like this, it is ok:
mDisposable = RxBus.listen(DataEvent::class.java).subscribe({
onDataReady(it)
})
but when call the RxBus.listen(DataEvent::class.java).subscribe(observer) with defined observer instance
it shows red underline: “Type mismatch. Required: Disposable? Found: Unit”
mDisposable = RxBus.listen(DataEvent::class.java).subscribe(observer)
the observer is:
var observer: Observer<DataEvent> = object : Observer<DataEvent> {
override fun onSubscribe(d: Disposable) {
Log.e(TAG, "onSubscribe: ")
}
override fun onNext(#NonNull t: DataEvent) {
Log.e(TAG, "onNext: " + t)
onDataReady(t)
}
override fun onError(e: Throwable) {
Log.e(TAG, "onError: ")
}
override fun onComplete() {
Log.e(TAG, "onComplete: All Done!")
}
}
It's because in RxJava 2.0 method subscribe(observer) was changed and return nothing.
Unlike the Observable of version 1.x, subscribe(Observer) does not allow external cancellation of a subscription and the Observer instance is expected to expose such capability.
You can use subscribeWith(observer).
Example:
val disposable = Observable.just("Hello world!")
.delay(1, TimeUnit.SECONDS)
.subscribeWith(object : DisposableObserver<String>() {
public override fun onStart() {
println("Start!")
}
fun onNext(t: Int?) {
println(t)
}
override fun onError(t: Throwable) {
t.printStackTrace()
}
override fun onComplete() {
println("Done!")
}
})