I am trying to implement this complex API structure.
So I tried to implement it with RxJava2 zip for parallel requests
private fun getDetails(marketDataQuotes: MarketDataQuotes, instrumentById: InstrumentById, subscribe: Subscribe): Observable<DetailsWatchListModel> {
return Observable.zip(
getMarketDataQutoes(marketDataQuotes),
getInstrumentById(instrumentById),
getSubscribeInstrument(subscribe),
Function3<MarketDataQuotesResponse, List<InstrumentByIdResponse>, SubscribeResult,DetailsWatchListModel>
{ marketData, instrumentList, subscribeInstrument ->
detailWatchList(marketData, instrumentList, subscribeInstrument)
})
}
but facing this Issue
private fun getSubscribeInstrument(subscribe: Subscribe): LiveData<SubscribeResult> {
val mutableLiveData = MutableLiveData<SubscribeResult>()
remoteServices.requestSubscribe(subscribe)
.subscribeOn(Schedulers.io())
// .observeOn(AndroidSchedulers.mainThread())
.subscribeWith(object : ErrorCallBack<BaseResponse<SubscribeResult>>() {
override fun onSuccess(t: BaseResponse<SubscribeResult>) {
L.d("Success of Market data Quotes")
// mutableLiveData.value = transform(t)
}
})
return mutableLiveData
}
And other API calls are like this with single place error handling and base Response Structre
And Service like
#Headers("Content-Type: application/json")
#POST("instruments/subscription")
fun requestSubscribe(#Body subscribe: Subscribe): Observable<BaseResponse<SubscribeResult>>
Using Kotlin v1.2.21 , retofit 2.3.0 , RxJava2 2.1.5 Please let me know what i am doing wrong.. Tanx in Advance
LiveData cannot be zipped with RxJava.
You need to use this API: https://developer.android.com/reference/android/arch/lifecycle/LiveDataReactiveStreams.html
So convert your LiveData to RxJava and then zip it.
Related
I have an external interface which I cannot change:
interface ExternalApi {
fun onDataReceived(data: String)
}
I need to start consuming data and send it to flow. Data order is a necessity. I'd like to have a cold flow, but I couldn't find a version of cold flow with emit function, so I used hot flow + replay set to Max value as a workaround. Here was my first try:
class FlowProblem {
val flow: MutableSharedFlow<String> = MutableSharedFlow(replay = Int.MAX_VALUE)
fun startConsuming() {
object : ExternalApi {
override fun onDataReceived(data: String) {
flow.emit(data)
}
}
}
}
Unfortunately it doesn't work as emit function is a suspended function. However this is an external interface and I cannot add suspend modifier. I tried to also do something like this:
override fun onDataReceived(data: String) {
val coroutineScope = CoroutineScope(Job())
coroutineScope.launch {
flow.emit(data)
}
}
but for me it's kind a silly to create new coroutine only in order to move data to flow. I'm also wondering about data order.
What should I do? Maybe flow/channel is not suitable here and I should pick something another?
Thanks IR42, callbackFlow was exactly what I needed.
I consider why there is no Retrofit2 adapter for Flow type like FlowCallAdapter and FlowCallAdapterFactory?
I found that retrofit supports suspend functions, and http request are one-shot so it better fits to have suspend functions rather then Flow return type, but Flow is more equivalent of RxJava and reactive programming frameworks and better fit in this schema of programming
How I can do something like this withoud Retrofit returning Flow and instead having suspend function
rules
.filter { it.isAsync }
.asFlow()
.flatMapMerge {
val rule = it
rule.validateAsync(input)
.filter { !it }
.map { rule }
}
.scan(mutableListOf<String>()) { acc, rule ->
acc.add(rule.errorMessage)
acc
}
.flowOn(Dispatchers.IO)
If I am using suspend I need to do something like this generally
return flow { emit(service.validate(email = value)) }
Because HTTP does not return a sequence of responses, only one response at a time. They replied they are not going to support that
https://github.com/square/retrofit/issues/3497
But you can implement your own CallAdapter to use flow
I wanted to know how can I send/emit items to a Kotlin.Flow, so my use case is:
In the consumer/ViewModel/Presenter I can subscribe with the collect function:
fun observe() {
coroutineScope.launch {
// 1. Send event
reopsitory.observe().collect {
println(it)
}
}
}
But the issue is in the Repository side, with RxJava we could use a Behaviorsubject expose it as an Observable/Flowable and emit new items like this:
behaviourSubject.onNext(true)
But whenever I build a new flow:
flow {
}
I can only collect. How can I send values to a flow?
If you want to get the latest value on subscription/collection you should use a ConflatedBroadcastChannel:
private val channel = ConflatedBroadcastChannel<Boolean>()
This will replicate BehaviourSubject, to expose the channel as a Flow:
// Repository
fun observe() {
return channel.asFlow()
}
Now to send an event/value to that exposed Flow simple send to this channel.
// Repository
fun someLogicalOp() {
channel.send(false) // This gets sent to the ViewModel/Presenter and printed.
}
Console:
false
If you wish to only receive values after you start collecting you should use a BroadcastChannel instead.
To make it clear:
Behaves as an Rx's PublishedSubject
private val channel = BroadcastChannel<Boolean>(1)
fun broadcastChannelTest() {
// 1. Send event
channel.send(true)
// 2. Start collecting
channel
.asFlow()
.collect {
println(it)
}
// 3. Send another event
channel.send(false)
}
false
Only false gets printed as the first event was sent before collect { }.
Behaves as an Rx's BehaviourSubject
private val confChannel = ConflatedBroadcastChannel<Boolean>()
fun conflatedBroadcastChannelTest() {
// 1. Send event
confChannel.send(true)
// 2. Start collecting
confChannel
.asFlow()
.collect {
println(it)
}
// 3. Send another event
confChannel.send(false)
}
true
false
Both events are printed, you always get the latest value (if present).
Also, want to mention Kotlin's team development on DataFlow (name pending):
https://github.com/Kotlin/kotlinx.coroutines/pull/1354
Which seems better suited to this use case (as it will be a cold stream).
Take a look at MutableStateFlow documentation as it is a replacement for ConflatedBroadcastChannel that is going to be deprecated, very soon.
For a better context, look at the whole discussion on the original issue on Kotlin's repository on Github.
UPDATE:
Kotlin Coroutines 1.4.0 is now available with MutableSharedFlow, which replaces the need for Channel. MutableSharedFlow cleanup is also built in so you don't need to manually OPEN & CLOSE it, unlike Channel. Please use MutableSharedFlow if you need a Subject-like api for Flow
ORIGINAL ANSWER
Since your question had the android tag I'll add an Android implementation that allows you to easily create a BehaviorSubject or a PublishSubject that handles its own lifecycle.
This is relevant in Android because you don't want to forget to close the channel and leak memory. This implementation avoids the need to explicitly "dispose" of the reactive stream by tying it to the creation and destruction of the Fragment/Activity. Similar to LiveData
interface EventReceiver<Message> {
val eventFlow: Flow<Message>
}
interface EventSender<Message> {
fun postEvent(message: Message)
val initialMessage: Message?
}
class LifecycleEventSender<Message>(
lifecycle: Lifecycle,
private val coroutineScope: CoroutineScope,
private val channel: BroadcastChannel<Message>,
override val initialMessage: Message?
) : EventSender<Message>, LifecycleObserver {
init {
lifecycle.addObserver(this)
}
override fun postEvent(message: Message) {
if (!channel.isClosedForSend) {
coroutineScope.launch { channel.send(message) }
} else {
Log.e("LifecycleEventSender","Channel is closed. Cannot send message: $message")
}
}
#OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
fun create() {
channel.openSubscription()
initialMessage?.let { postEvent(it) }
}
#OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun destroy() {
channel.close()
}
}
class ChannelEventReceiver<Message>(channel: BroadcastChannel<Message>) :
EventReceiver<Message> {
override val eventFlow: Flow<Message> = channel.asFlow()
}
abstract class EventRelay<Message>(
lifecycle: Lifecycle,
coroutineScope: CoroutineScope,
channel: BroadcastChannel<Message>,
initialMessage: Message? = null
) : EventReceiver<Message> by ChannelEventReceiver<Message>(channel),
EventSender<Message> by LifecycleEventSender<Message>(
lifecycle,
coroutineScope,
channel,
initialMessage
)
By using the Lifecycle library from Android, I can now create a BehaviorSubject that cleans itself up after the activity/fragment has been destroyed
class BehaviorSubject<String>(
lifecycle: Lifecycle,
coroutineScope: CoroutineScope,
initialMessage = "Initial Message"
) : EventRelay<String>(
lifecycle,
coroutineScope,
ConflatedBroadcastChannel(),
initialMessage
)
or I can create a PublishSubject by using a buffered BroadcastChannel
class PublishSubject<String>(
lifecycle: Lifecycle,
coroutineScope: CoroutineScope,
initialMessage = "Initial Message"
) : EventRelay<String>(
lifecycle,
coroutineScope,
BroadcastChannel(Channel.BUFFERED),
initialMessage
)
And now I can do something like this
class MyActivity: Activity() {
val behaviorSubject = BehaviorSubject(
this#MyActivity.lifecycle,
this#MyActivity.lifecycleScope
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState == null) {
behaviorSubject.eventFlow
.onEach { stringEvent ->
Log.d("BehaviorSubjectFlow", stringEvent)
// "BehaviorSubjectFlow: Initial Message"
// "BehaviorSubjectFlow: Next Message"
}
.flowOn(Dispatchers.Main)
.launchIn(this#MyActivity.lifecycleScope)
}
}
override fun onResume() {
super.onResume()
behaviorSubject.postEvent("Next Message")
}
}
I come from Java and I'm following a tutorial online regarding using the Volley library to make web requests in Android.
The instructor created the request variable like this:
val registerRequest = object : StringRequest(Method.POST, URL_REGISTER, Response.Listener {
println(it) // will print the response
complete(true)
}, Response.ErrorListener {
Log.d("ERROR", "Could not register user: $it")
complete(false)
}) {
override fun getBodyContentType(): String {
return "application/json; charset=utf-8"
}
override fun getBody(): ByteArray {
return requestBody.toByteArray()
}
}
I understand that he's creating a registerRequest variable of type StringRequest. But what I don't understand is why he prefixed StringRequest with object : here.
Also I understand that StringRequest constructor takes in an Int, String, Lambda, Lambda. After that it becomes confusing to me because the developer was able to declare some override methods after the constructor closes. Why did they do this? From what I can tell, this is similar to subclassing StringRequest, then writing the override methods there? Am I right?
Coming from Java, this way of writing code is quite unusual to me.
I use webflux with netty and jdbc, so I wrap blocking jdbc operation the next way:
static <T> Mono<T> fromOne(Callable<T> blockingOperation) {
return Mono.fromCallable(blockingOperation)
.subscribeOn(jdbcScheduler)
.publishOn(Schedulers.parallel());
}
Blocking operation will be processed by the jdbcScheduler, and I want the other pipeline will be proccesed by webflux event-loop scheduler.
How to get webflux event-loop scheduler?
I will strongly advise to revisit the technology options. If you are going to use jdbc, which is still blocking, then you should not use webflux. This is because webflux will shine in a non-blocking stack but coupled with Jdbc it will act as a bottleneck. The performance will actually go down.
I agree with #Vikram Rawat use jdbc is very dangerous mainly because jdbc is a bocking IO api and use an event loop reactive model is very dangerous because basically it is very easy block all the server.
However, even if it is an experimental effort I suggest you to stay tuned to R2DBC project that is able to leverage a no blocking api for sql I used it for a spike and it is very elegant.
I can provide you an example taken from a my home project on github based on sprign boot 2.1 and kotlin:
web layer
#Configuration
class ReservationRoutesConfig {
#Bean
fun reservationRoutes(#Value("\${baseServer:http://localhost:8080}") baseServer: String,
reservationRepository: ReservationRepository) =
router {
POST("/reservation") {
it.bodyToMono(ReservationRepresentation::class.java)
.flatMap { Mono.just(ReservationRepresentation.toDomain(reservationRepresentation = it)) }
.flatMap { reservationRepository.save(it).toMono() }
.flatMap { ServerResponse.created(URI("$baseServer/reservation/${it.reservationId}")).build() }
}
GET("/reservation/{reservationId}") {
reservationRepository.findOne(it.pathVariable("reservationId")).toMono()
.flatMap { Mono.just(ReservationRepresentation.toRepresentation(it)) }
.flatMap { ok().body(BodyInserters.fromObject(it)) }
}
DELETE("/reservation/{reservationId}") {
reservationRepository.delete(it.pathVariable("reservationId")).toMono()
.then(noContent().build())
}
}
}
repository layer:
class ReactiveReservationRepository(private val databaseClient: TransactionalDatabaseClient,
private val customerRepository: CustomerRepository) : ReservationRepository {
override fun findOne(reservationId: String): Publisher<Reservation> =
databaseClient.inTransaction {
customerRepository.find(reservationId).toMono()
.flatMap { customer ->
it.execute().sql("SELECT * FROM reservation WHERE reservation_id=$1")
.bind("$1", reservationId)
.exchange()
.flatMap { sqlRowMap ->
sqlRowMap.extract { t, u ->
Reservation(t.get("reservation_id", String::class.java)!!,
t.get("restaurant_name", String::class.java)!!,
customer, t.get("date", LocalDateTime::class.java)!!)
}.one()
}
}
}
override fun save(reservation: Reservation): Publisher<Reservation> =
databaseClient.inTransaction {
customerRepository.save(reservation.reservationId, reservation.customer).toMono()
.then(it.execute().sql("INSERT INTO reservation (reservation_id, restaurant_name, date) VALUES ($1, $2, $3)")
.bind("$1", reservation.reservationId)
.bind("$2", reservation.restaurantName)
.bind("$3", reservation.date)
.fetch().rowsUpdated())
}.then(Mono.just(reservation))
override fun delete(reservationId: String): Publisher<Void> =
databaseClient.inTransaction {
customerRepository.delete(reservationId).toMono()
.then(it.execute().sql("DELETE FROM reservation WHERE reservation_id = $1")
.bind("$1", reservationId)
.fetch().rowsUpdated())
}.then(Mono.empty())
}
I hope that can help you