How to prevent Rxjava Subject on "onComplete"? - rx-java2

I need a RxJava subject that ignore onComplete(), So even I used RxRelay, it call onComplete yet :(
private val mDisposables = CompositeDisposable()
private val mRelay: BehaviorRelay<Boolean> = BehaviorRelay.createDefault(true)
....
mDisposables += mRelay
.observeOn(io)
.throttleLatest(5, SECONDS)
.flatMap { ... }
.zipWith(...)
.switchMap {...}
.subscribeOn(io)
.subscribeWith(object : DisposableObserver<UiData>() {
override fun onComplete() {
Timber.d("COMPLETED")
}
override fun onNext(it: UiData) {
Timber.d(it.toString())
}
override fun onError(e: Throwable) {
Timber.e(e)
}
})
}
Why always do onComplete call?

It's probably your usage of .zipWith which limits the stream to the shortest participant, regardless of whether the other streams ever finish.

I realized .zipWith() breaks stream. Finally I replaced .zipWith() with .flatMap() .
Thanks #Kiskae

Related

How to know, if unmarshell was successful or not

I have a route, that will unmarshell the incoming entity into a case class.
final case class ProducerMessage(topic: String, event: String, data: spray.json.JsObject)
object ProducerServer {
private val route: Route =
path("producer") {
post {
entity(as[ProducerMessage]) { msg =>
//complete(HttpEntity(ContentTypes.`text/html(UTF-8)`, "<h1>Say hello to akka-http</h1>"))
}
}
}
def create(): Future[ServerBinding] {
Http().bindAndHandle(route, getServerIp, getServerPort)
}
}
How do I know, if the process of unmarshell was successfully or not?
When received data is not a valid JSON format, what happen then?
When you have entity(as[T]) the as[T] is used to summon instance of FromRequestUnmarshaller[T] - then depending of the result returned by unmarshaller, entity will continue with passing on T into closure, or if it will fail the Directive.
If you need to do something with the information about rejection, then there are methods like recover, which you can call before apply.
For instance:
entity(as[ProducerMessage])
.map(Right(_): Either[Seq[Rejection], ProducerMessage])
.recover { rejections =>
provide(Left(rejections): Either[Seq[Rejection], ProducerMessage]))
} { value: Either[Seq[Rejection], ProducerMessage] =>
...
}
should let you see if input was rejected and recover/handle it manually.

RxJava2/RxAndroidBle: subscribe to Observable from side effects

I have the following use case of a simple BLE device setup process using RxAndroidBle:
Connect to a BLE device.
Start listening to notification characteristic and set up a parser to parse each incoming notification. Parser will then use a PublishSubject to publish parsed data.
Perform a write to write characteristic (negotiate secure connection).
Wait for parser PublishSubject to deliver the parsed response from device - public key (which arrived through the notification characteristic as a response to our write).
Perform another write to the write characteristic (set connection as secure).
Deliver a Completable saying if the process has completed successfully or not.
Right now my solution (not working) looks like this:
deviceService.connectToDevice(macAddress)
.andThen(Completable.defer { deviceService.setupCharacteristicNotification() })
.andThen(Completable.defer { deviceService.postNegotiateSecurity() })
.andThen(Completable.defer {
parser.notificationResultSubject
.flatMapCompletable { result ->
when (result) {
DevicePublicKeyReceived -> Completable.complete()
else -> Completable.error(Exception("Unexpected notification parse result: ${result::class}"))
}
}
})
.andThen(Completable.defer { deviceService.postSetSecurity() })
And the DeviceService class:
class DeviceService {
/**
* Observable keeping shared RxBleConnection for reuse by different calls
*/
private var connectionObservable: Observable<RxBleConnection>? = null
fun connectToDevice(macAddress: String): Completable {
return Completable.fromAction {
connectionObservable =
rxBleClient.getBleDevice(macAddress)
.establishConnection(false)
.compose(ReplayingShare.instance())
}
}
fun setupCharacteristicNotification(): Completable =
connectionObservable?.let {
it
.switchMap { connection ->
connection.setupNotification(UUID_NOTIFICATION_CHARACTERISTIC)
.map { notificationObservable -> notificationObservable.doOnNext { bytes -> parser.parse(bytes) }.ignoreElements() }
.map { channel ->
Observable.merge(
Observable.never<RxBleConnection>().startWith(connection),
channel.toObservable()
)
}
.ignoreElements()
.toObservable<RxBleConnection>()
}
.doOnError { Timber.e(it, "setup characteristic") }
.take(1).ignoreElements()
} ?: Completable.error(CONNECTION_NOT_INITIALIZED)
fun postNegotiateSecurity(): Completable {
val postLength = negotiateSecurity.postNegotiateSecurityLength()
val postPGK = negotiateSecurity.postNegotiateSecurityPGKData()
return connectionObservable?.let {
it.take(1)
.flatMapCompletable { connection ->
postLength
.flatMapSingle { connection.write(it.bytes.toByteArray()) }
.doOnError { Timber.e(it, "post length") }
.flatMap {
postPGK
.flatMapSingle { connection.write(it.bytes.toByteArray()) }
.doOnError { Timber.e(it, "post PGK") }
}
.take(1).ignoreElements()
}
} ?: Completable.error(CONNECTION_NOT_INITIALIZED)
}
fun postSetSecurity(): Completable =
connectionObservable?.let {
it.take(1)
.flatMapCompletable { connection ->
negotiateSecurity.postSetSecurity()
.flatMapSingle { connection.write(it.bytes.toByteArray()) }
.take(1).ignoreElements()
}
} ?: Completable.error(CONNECTION_NOT_INITIALIZED)
}
private fun RxBleConnection.write(bytes: ByteArray): Single<ByteArray> =
writeCharacteristic(UUID_WRITE_CHARACTERISTIC, bytes)
The problem is that it gets stuck in deviceService.postNegotiateSecurity() and never gets past. I don't get any data in the parser as well, so I assume I'm incorrectly subscribing to the notification characteristic.
negotiateSecurity.postNegotiateSecurityLength() and negotiateSecurity.postNegotiateSecurityPGKData() are methods which prepare data to be sent and deliver it as Observable<SendFragment>. Because of data frame size limit, one frame might be encoded as several fragments, which are then emitted by these Observables.
Recap:
postNegotiateSecurity() is never completed
negotiateSecurity.postNegotiateSecurityLength() may emit one or more times
negotiateSecurity.postNegotiateSecurityPGKData() may emit one or more times
Analysis (omitted logs for readability):
it.take(1)
.flatMapCompletable { connection ->
postLength
.flatMapSingle { connection.write(it.bytes.toByteArray()) }
.flatMap {
postPGK // may emit more than one value
.flatMapSingle { connection.write(it.bytes.toByteArray()) }
}
.take(1) // first emission from the above `flatMap` will finish the upstream
.ignoreElements()
}
Every emission from postLength will start a characteristic write. Every succeeded write will start subscription to postPGK. If postLength will emit more than once — more subscriptions to postPGK will be made.
Every subscription to postPGK most likely will result in multiple emissions. Every emission will then be flatMapped to a characteristic write. Every write succeeded write will emit a value.
After the first emission from the above mentioned characteristic write the upstream will be disposed (because of .take(1) operator).
If the postNegotiateSecurity() is actually started it will also finish or error (given that both postLength and postPGK will emit at least one value) since there is no additional logic here.
Conclusion
postNegotiateSecurity() will most probably complete (but not in an intended manner) as the first packet from postPGK will finish it. I would assume that the peripheral expects full data before it will notify anything therefore it is waiting for the PGK to be fully transmitted which will not happen as shown above.
Logs from the application with RxBleLog.setLogLevel(RxBleLog.VERBOSE) set on could help with understanding of what actually happened.

RxSwift retry full chain

I'm pretty new in RxSwift and I have the following problem.
Given two functions:
struct Checkout { ... }
func getSessionIdOperation() -> Single<UUID>
func getCheckoutForSession(_ sessionId: UUID, asGuestUser: Bool) -> Single<Checkout>
I have a third function that combines the result of the two:
func getCheckout(asGuestUser: Bool) -> Single<Checkout> {
return getSessionIdOperation()
.map { ($0, asGuestUser) }
.flatMap(getCheckoutForSession)
}
both getSessionIdOperationand getCheckoutForSession can fail, and in case of failure I would like to restart the whole chain just once. I tried retry(2) but just getCheckoutForSession was repeated. :(
Make sure you retry(2) on stream with flatMap
func getCheckout(asGuestUser: Bool) -> Single<Checkout> {
return getSessionIdOperation()
// .retry(2) will retry just first stream
.map { ($0, asGuestUser) }
.flatMap(getCheckoutForSession)
.retry(2) // Here it will retry whole stream
}
In case getSessionIdOperation will fail getCheckoutForSession will never be called as it's based on output from first stream.

RxSwift, Share + retry mechanism

I have a network request that can Succeed or Fail
I have encapsulated it in an observable.
I have 2 rules for the request
1) There can never be more then 1 request at the same time
-> there is a share operator i can use for this
2) When the request was Succeeded i don't want to repeat the same
request again and just return the latest value
-> I can use shareReplay(1) operator for this
The problem arises when the request fails, the shareReplay(1) will just replay the latest error and not restart the request again.
The request should start again at the next subscription.
Does anyone have an idea how i can turn this into a Observable chain?
// scenario 1
let obs: Observable<Int> = request().shareReplay(1)
// outputs a value
obs.subscribe()
// does not start a new request but outputs the same value as before
obs.subscribe()
// scenario 2 - in case of an error
let obs: Observable<Int> = request().shareReplay(1)
// outputs a error
obs.subscribe()
// does not start a new request but outputs the same value as before, but in this case i want it to start a new request
obs.subscribe()
This seems to be a exactly doing what i want, but it consists of keeping state outside the observable, anyone know how i can achieve this in a more Rx way?
enum Err: Swift.Error {
case x
}
enum Result<T> {
case value(val: T)
case error(err: Swift.Error)
}
func sample() {
var result: Result<Int>? = nil
var i = 0
let intSequence: Observable<Result<Int>> = Observable<Int>.create { observer in
if let result = result {
if case .value(let val) = result {
return Observable<Int>.just(val).subscribe(observer)
}
}
print("do work")
delay(1) {
if i == 0 {
observer.onError(Err.x)
} else {
observer.onNext(1)
observer.onCompleted()
}
i += 1
}
return Disposables.create {}
}
.map { value -> Result<Int> in Result.value(val: value) }
.catchError { error -> Observable<Result<Int>> in
return .just(.error(err: error))
}
.do(onNext: { result = $0 })
.share()
_ = intSequence
.debug()
.subscribe()
delay(2) {
_ = intSequence
.debug()
.subscribe()
_ = intSequence
.debug()
.subscribe()
}
delay(4) {
_ = intSequence
.debug()
.subscribe()
}
}
sample()
it only generates work when we don't have anything cached, but thing again we need to use side effects to achieve the desired output
As mentioned earlier, RxSwift errors need to be treated as fatal errors. They are errors your stream usually cannot recover from, and usually errors that would not even be user facing.
For that reason - a stream that emits an .error or .completed event, will immediately dispose and you won't receive any more events there.
There are two approaches to tackling this:
Using a Result type like you just did
Using .materialize() (and .dematerialize() if needed). These first operator will turn your Observable<Element> into a Observable<Event<Element>>, meaning instead of an error being emitted and the sequence terminated, you will get an element that tells you it was an error event, but without any termination.
You can read more about error handling in RxSwift in Adam Borek's great blog post about this: http://adamborek.com/how-to-handle-errors-in-rxswift/
If an Observable sequence emits an error, it can never emit another event. However, it is a fairly common practice to wrap an error-prone Observable inside of another Observable using flatMap and catch any errors before they are allowed to propagate through to the outer Observable. For example:
safeObservable
.flatMap {
Requestor
.makeUnsafeObservable()
.catchErrorJustReturn(0)
}
.shareReplay(1)
.subscribe()

RxSwift repeated action

I'm switching from RAC and want to have a repeated network request, returning different result types depending on the API of the request.
I want to use an interval, but I don't know how to match the return types.
var loop: Observable<Element> {
return Observable<Int>.interval(5.0, scheduler: MainScheduler.instance).map { _ in
// Do network request and return Observable<Element>
}
}
I need to invoke Observerable.interval with type Int - but return Observable. How would I do that?
Use flatMap:
var loop: Observable<Element> {
return Observable<Int>.interval(5.0, scheduler: MainScheduler.instance).flatMap { _ in
return networkRequest() // returns Observable<Element>
}
}