i have a chain of calls from a presenter to repository which returns an observable. This is the code:
Presenter:
private fun getCategories() =
compositeDisposable.add(
categoriesUseCase.getCategories()
.timeout(TIMEOUT, TIMEOUT_UNIT)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::handleCategories, this::handleCategoriesTimeout)
)
This is the usecase:
fun getCategories(): Observable<List<Category>> =
repository.getCategories()
.map { it.map { Category(it.id, it.text, it.icon) } }
This is the repo: //subject is BehaviorSubject.create()
fun getcategories(): Observable<List<DiscoverabilityCategoryElement>> =
Observable.just(storage.getCategories())
.mergeWith { subject.flatMapIterable { it.categories }.publish() }
.subscribeOn(Schedulers.io())
.doOnNext { Logger.d("Data", "next categories $it") }
.filter { it.isPresent }
.map { it.get() }
.take(1)
.doOnSubscribe { Logger.d("Data", "Subcribed categories") }
fun saveApiResult(response: Response) {//This is being called after subscribe
subject.onNext(response.categories)
subject.onComplete()
}
Method on storage will always return Optional.empty() (Meanwhile i'm developing)
My problem is, even seeing that subject.onNext is being called, that value never comes to the presenter, i've debug a bit and subject always returns false to hasObservables, maybe i'm losing my observer in some point?
Why do you call publish() on that line? It returns a ConnectableObservable which does nothing until connect is called. However, there is nothing on that line that would require sharing.
Try this:
fun getcategories(): Observable<List<DiscoverabilityCategoryElement>> =
Observable.just(storage.getCategories())
.mergeWith { subject.flatMapIterable { it.categories } } // <-------------
.subscribeOn(Schedulers.io())
.doOnNext { Logger.d("Data", "next categories $it") }
.filter { it.isPresent }
.map { it.get() }
.take(1)
.doOnSubscribe { Logger.d("Data", "Subcribed categories") }
Solution was change
.mergeWith { subject.flatMapIterable { it.categories }.publish() }
by
.mergeWith(subject.flatMap({ rootElement -> Observable.fromArray(element.categories.toOptional()) }))
Related
I want to chain 3 coroutines by using Kotlin's extension functions. I know how to do it with regular ones, but can't manage it with extension functions. In fact, in the 2nd coroutine I can receive only one data sent from the 1st coroutine, but that's all. The program works but all I get on the console is Doc: 1st Document. What I'm doing wrong?
fun main(args: Array<String>) = runBlocking {
produceDocs().docLength().report().consumeEach {
println(it)
}
}
private fun CoroutineScope.produceDocs() = produce {
fun getDocs(): List<String> {
return listOf("1st Document", "2nd Newer Document")
}
while (this.isActive) {
val docs = getDocs()
for (doc in docs) {
send(doc)
}
delay(TimeUnit.SECONDS.toMillis(2))
}
}
private suspend fun ReceiveChannel<String>.docLength(): ReceiveChannel<Int> = coroutineScope {
val docsChannel: ReceiveChannel<String> = this#docLength
produce {
for (doc in docsChannel) {
println("Doc: $doc") // OK. This works.
send(doc.count()) // ??? Not sure where this sends data to?
}
}
}
private suspend fun ReceiveChannel<Int>.report(): ReceiveChannel<String> = coroutineScope {
val docLengthChannel: ReceiveChannel<Int> = this#report
produce {
for (len in docLengthChannel) {
println("Length: $len") // !!! Nothing arrived.
send("Report. Document contains $len characters.")
}
}
}
You have to consume each channel independently in order to make emissions go through the chain, otherwise the first emission will never be consumed:
private fun CoroutineScope.produceDocs() = produce {
fun getDocs(): List<String> {
return listOf("1st Document", "2nd Newer Document")
}
while (this.isActive) {
val docs = getDocs()
for (doc in docs) {
send(doc)
}
delay(TimeUnit.SECONDS.toMillis(2))
}
}
private suspend fun ReceiveChannel<String>.docLength() : ReceiveChannel<Int> = CoroutineScope(coroutineContext).produce {
for (doc in this#docLength) {
println("Doc: $doc") // OK. This works.
send(doc.count()) // ??? Not sure where this sends data to?
}
}
private suspend fun ReceiveChannel<Int>.report(): ReceiveChannel<String> = CoroutineScope(coroutineContext).produce {
for (len in this#report) {
println("Length: $len") // !!! Nothing arrived.
send("Report. Document contains $len characters.")
}
}
I suggest you a better approach to do the exact same thing using Flow:
private fun produceDocs(): Flow<String> = flow {
fun getDocs(): List<String> {
return listOf("1st Document", "2nd Newer Document")
}
while (true) {
val docs = getDocs()
for (doc in docs) {
emit(doc)
}
delay(TimeUnit.SECONDS.toMillis(2))
}
}
private fun Flow<String>.docLength(): Flow<Int> = flow {
collect { doc ->
println("Doc: $doc")
emit(doc.count())
}
}
private fun Flow<Int>.report(): Flow<String> = flow {
collect { len ->
println("Length: $len")
emit("Report. Document contains $len characters.")
}
}
Or better like this:
private fun produceDocs(): Flow<String> = flow {
fun getDocs(): List<String> {
return listOf("1st Document", "2nd Newer Document")
}
while (true) {
val docs = getDocs()
for (doc in docs) {
emit(doc)
}
delay(TimeUnit.SECONDS.toMillis(2))
}
}
private fun Flow<String>.docLength(): Flow<Int> = transform { doc ->
println("Doc: $doc")
emit(doc.count())
}
private fun Flow<Int>.report(): Flow<String> = transform { len ->
println("Length: $len")
emit("Report. Document contains $len characters.")
}
And collect it like this:
produceDocs().docLength().report().collect {
println(it)
}
Or even better like this:
produceDocs()
.map { doc ->
println("Doc: $doc")
doc.count()
}
.map { len ->
println("Length: $len")
"Report. Document contains $len characters."
}
.collect {
println(it)
}
var responseMap = mutableMapOf<VendorType, ChargeResponse>()
requests.forEach {
val response = when (it.vendorType) {
VendorType.Type1 -> service.chargeForType1()
VendorType.Type2 -> service.chargeForType2()
else -> {
throw NotImplementedError("${it.vendorType} does not support yet")
}
}
responseMap[it.vendorType] = response
}
responseMap
So I want all the service.charge function run in separate thread. Return the map when all is done
Hope to solve your problem:
Assume your service and request like this:
interface Service {
suspend fun chargeForType1(): ChargeResponse
suspend fun chargeForType2(): ChargeResponse
}
data class Request(val vendorType: VendorType)
suspend fun requestAll(requests: List<Request>): Map<VendorType, ChargeResponse> {
return coroutineScope {
requests
.map { request ->
async {
request.vendorType to when (request.vendorType) {
VendorType.Type1 -> service.chargeForType1()
VendorType.Type2 -> service.chargeForType2()
else -> throw NotImplementedError("${request.vendorType} does not support yet")
}
}
}
.awaitAll()
.toMap()
}
}
Please have a look at the piece of code below. Now suppose i'll have hundreds of entity like "person". How would you code such a thing to get it clean, concise, efficient, well structured ? Tx
class HttpEntryPoint : CoroutineVerticle() {
private suspend fun person(r: RoutingContext) {
val res = vertx.eventBus().requestAwait<String>("/person/:id", "1").body()
r.response().end(res)
}
override suspend fun start() {
val router = Router.router(vertx)
router.get("/person/:id").coroutineHandler { ctx -> person(ctx) }
vertx.createHttpServer()
.requestHandler(router)
.listenAwait(config.getInteger("http.port", 8080))
}
fun Route.coroutineHandler(fn: suspend (RoutingContext) -> Unit) {
handler { ctx ->
launch(ctx.vertx().dispatcher()) {
try {
fn(ctx)
} catch (e: Exception) {
e.printStackTrace()
ctx.fail(e)
}
}
}
}
}
You're looking for subrouter.
https://vertx.io/docs/vertx-web/java/#_sub_routers
From the top of my head:
override suspend fun start() {
router.mountSubrouter("/person", personRouter(vertx))
// x100 if you'd like
}
Then in your PersonRouter.kt:
fun personRouter(vertx: Vertx): Router {
val router = Router.router(vertx)
router.get("/:id").coroutineHandler { ctx -> person(ctx) }
// More endpoints
return router
}
Try in rxJava2 Kotlin combine Single with Flowable but nothing not happening:
Does not undrstand what wrong
Flowable.create<Int>({ emmit ->
loadNewListener = object :Listener {
override fun onEmit(id: Int) {
emmit.onNext(id)
}
}
}, BackpressureStrategy.LATEST)
.debounce(500, TimeUnit.MILLISECONDS)
.flatMapSingle {
loadNew(id = it.id)
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ (data:Data) ->
}, {
Timber.e("Failed load data ${it.message}")
})
my method is returning Single:
private fun loadNew(id: Int): Single<Data> {
return when (pdfType) {
CASE_0 -> {
Single.create<Data> { emmit ->
service.get("data")
.enqueue(
object : Callback<Void> {
override fun onFailure(call: Call<Void>?, t: Throwable?) {
// failure
}
override fun onResponse(call: Call<Void>?, response: Response<Void>?) {
emmit.onSuccess(it.data)
}
}
}//single
}//case_0
CASE_1 -> 1Repository.loadsome1Rx(id = id).map { it.getData() }
CASE_2 -> 2Repository.loadsom2LocalRx(id = id).map { it.getData() }
else -> {
throw java.lang.RuntimeException("$this is not available type!")
}
}
What is wrong im my code?
Need Maby call Single in Flowable subscribe() seppurate
like this?
Flowable.create<Int>({ emmit ->
loadNewListener = object :Listener {
override fun onEmit(id: Int) {
emmit.onNext(id)
}
}
}, BackpressureStrategy.LATEST)
.debounce(500, TimeUnit.MILLISECONDS)
.subscribe({
loadNew(id = it.id)
}, {
Timber.e("")
})
This code is workin but looks not simple as via combine try.
This simple example based on your code is working
var i = 0
fun foo() {
Flowable.create<Int>({ emmit ->
emmit.onNext(i)
i++
}, BackpressureStrategy.LATEST)
.debounce(500, TimeUnit.MILLISECONDS)
.flatMapSingle {
Single.create<String> { emmit ->
emmit.onSuccess("onSuccess: $it")
}
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
Log.i("RX", "Subscribe: $it")
}, {
it.printStackTrace()
})
}
Check SingleEmitter.onSuccess() and SingleEmitter.onError() is called in all cases in when (pdfType)...
As #Stas Bondar said in answer below This simple example based on your code is working!!
Problem was in loadNewListener .
It does not init in time and has null value when need. Call create Flowable on init ViewModel but loadNewListener did not have time to create when i call him from fragment.
loadNewListener = object :Listener{...}
Becuse need some time mutch for init rxJava expression!
And combine flowable with single via flatMapSingle spent more time than just call single on flowable dubscrinbe!
So use temp field:
private var temp: Temp? = null
fun load(id: Int) {
loadNewListener.apply {
when {
this != null -> load(id = id)
else -> userEmitPdfTemp = Temp(id = id)
}
}
}
Flowable.create<Data>({ emmit ->
userEmitPdfTemp?.let {id->
emmit.onNext(Data(id))
userEmitPdfTemp =null
}
loadNewListener = object :Listener {
override fun load(id: Int) {
emmit.onNext(Data(id))
}
}
}
I created a function which returns an Observable<String> with file names, but I don't get any event in my subscription where I call this method. Also there is no call of onError, or onComplete
See my code:
fun getAllFiles(): Observable<String> {
val allFiles = File("/Users/stephan/Projects/Playground/kotlinfiles/")
.listFiles { file -> !file.isDirectory() }
return observable { subscriber ->
allFiles.toObservable()
.map { f -> "${f.name}" }
.doOnNext { println("Found file $it") }
.subscribe { subscriber}
}
}
fun test() {
getAllFiles()
.doOnNext { println("File name$it") }
.subscribe(
{n -> println("File: $n")},
{e -> println("Damn: $e")},
{println("Completed")})
}
Though everything is being called in the getAllFiles() function, so what am I missing?
observable is for creating an Observable from scratch but you already have Observable<String> from toObservable() so you don't need it. The code below works for me:
fun getAllFiles(): Observable<String> {
val allFiles = File("/Users/stephan/Projects/Playground/kotlinfiles/")
.listFiles { file -> !file.isDirectory }
return allFiles.toObservable()
.map { f -> "${f.name}" }
}
fun test() {
getAllFiles()
.doOnNext { println("File name $it") }
.subscribe(
{ n -> println("File: $n") },
{ e -> println("Damn: $e") },
{ println("Completed") })
}
You can also fix this by changing from:
.subscribe{subscriber}
to
.subscribe(subscriber)
but this nested Observable version is confusing to me.