I'm working on a trading app. When the user select some products I need to show for each product if the market is open and its latest price. The user can select for example 2 products and what I have to do is to show the data only when I have all the info for the 2 products. The data can change at any time (i.e. the market for one of the products got closed). This is my code:
data class ProductInfo(
val productCode: String,
val isMarketOpen: Boolean,
val latestPrice: BigDecimal,
)
// This observable could emit at any time due to user interaction with the UI
private fun productsCodeObservable(): Observable<List<String>> = Observable.just(listOf("ProductA", "ProductB"))
// Markets have different working hours
private fun isMarketOpenObservable(productCode: String): Observable<Boolean> {
return Observable.interval(2, TimeUnit.SECONDS)
.map {
// TODO: Use API to determine if the market is open for productCode
it.toInt() % 2 == 0
}
}
// The product price fluctuates so this emits every X seconds
private fun latestPriceObservable(productCode: String): Observable<BigDecimal> {
return Observable.interval(2, TimeUnit.SECONDS)
.map { productPrice -> productPrice.toBigDecimal() }
}
#Test
fun `test`() {
val countDownLatch = CountDownLatch(1)
productsCodeObservable()
.switchMap { Observable.fromIterable(it) }
.flatMap { productCode ->
Observable.combineLatest(
isMarketOpenObservable(productCode),
latestPriceObservable(productCode)
) { isMarketOpen, latestPrice ->
ProductInfo(productCode, isMarketOpen, latestPrice)
}
}
.toList()
.doOnSuccess { productsInfo ->
println(productsInfo)
}
.subscribe()
countDownLatch.await()
}
I don't know what the problem is because the test method never prints anything. I don't know much about RxJava but my understanding is that toList is not working because the source observables never complete. Any idea about how I can collect the data for the product codes and emit a list when any of the data changes? :)
If you want to receive new product info list every time any of these products has changed:
productsCodeObservable()
.switchMap { list ->
val productInfoObservables = list.map { productCode ->
Observable.combineLatest(
isMarketOpenObservable(productCode),
latestPriceObservable(productCode)
) { isMarketOpen, latestPrice ->
ProductInfo(productCode, isMarketOpen, latestPrice)
}
}
Observable.combineLatest(productInfoObservables) { it.toList() as List<ProductInfo> }
}
.doOnNext { productsInfoList ->
println(productsInfoList)
}
.subscribe()
Use the RxJava’s Emitter interface and implement its methods:
public interface Emitter<T> {
void onNext(T value);
void onError(Throwable error);
void onComplete();
}
I think you need an ObservableEmitter, Please take a look at the following page:
https://www.raywenderlich.com/2071847-reactive-programming-with-rxandroid-in-kotlin-an-introduction
Related
I want to loop multiple API requests after response of one API and on complete of all requests I have to perform action.
First API Response(getModels) =
{
"message":{
"models":[
{
"equipno":"000000000001027019",
},
{
"equipno":"000000000001027020",
},
{
"equipno":"000000000001027021",
},
{
"equipno":"FL3-PINRO-0001/011",
}
]
}
}
Then I have to call second API in loop for all equipno received in first API response .
Second API response(getPrediction) -
{"body":{
"status":"success",
"message":{
"equipno":"000000000001027019",
"predictions":[
{
"date":"2020-09-01",
"maintenance":true,
"accuracy":13.07
}
],
"elapsed":0.03
}
}
}
I tried to do using following Rx java code but its not working.
val predictiveMaintenanceServiceCall = PredictiveMaintenanceServiceCall(context)
val data = predictiveMaintenanceServiceCall.getModels(this).map { it.predictionMessage?.models?.map { it1 -> it1?.equipno } }
data.map { items ->
items.map { item ->
predictiveMaintenanceServiceCall.getPrediction(item, dateFormatter.format(Calendar.getInstance().time))
}
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ it ->
Log.i("v ", "loadAlertsList zip ${it.size}")
Observable.zip(it, Arrays::asList)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ it ->
for (i in it as List<PredictionModel>)
Log.i("v ", "loadAlertsList ${i.message?.equipno}")
}, {
Log.i("v ", "loadAlertsList ${it}")
})
},
{
})
}
First and foremost, if API is in your control then I suggest you update getPrediction API to except a list of equipno, so that you can call the API once and avoid all the unnesseary API calls.
As for how to do it currently, I suggest that instead of using RX you look into kotlin coroutines. your problem can be solved by following below steps.
Make your retrofit functions suspendable
Retrofit 2.6.0 and above support suspend function, so all you have to do is to mark your getPrediction and getModel with suspend modifier as
suspend fun getModel(): List<Model>
suspend fun getPredication(equipno: String): List<Predication>
Launch a coroutine to call API's (ideally this should be done in ViewModel)
private fun callAPI() = viewModelScope.launch {
val models = getModels()
models.forEach {
val predication = getPrediction(it.equipno)
// Do something with predication
}
}
I have the code:
return channelRepository.getRssFeedContent(channel.getSourceLink())
.toObservable()
.map(this::parseItem)
.flatMapIterable(xmlItemRawObjects -> xmlItemRawObjects)
.compose(/* question */)
.subscribeOn(Schedulers.from(threadExecutor))
.observeOn(postExecutionThread.getScheduler());
Using xmlItemRawObjects I need to make a query to the database, check if the record exists. If no record exists, return the same xmlItemRawObjects from compose() to continue working with it.
If the record exists in the database, then make sure that compose() skips the element for the stream.
I tried to create a function:
.compose(new ObservableTransformer<XmlItemRawObject, XmlItemRawObject>() {
#Override
public ObservableSource<XmlItemRawObject> apply(Observable<XmlItemRawObject> upstream) {
Boolean isExists = false;
Observable<Item> test = Observable.create(emitter -> {
upstream
.flatMap(xmlItemRawObject -> channelRepository.getItemByUniqueId(xmlItemRawObject.getGuid())
.subscribe(item -> isExists = true));
});
}
})
but it works just fine. Thank you for your advice.
P.S. No examples from compose() at all.
I've tried to solve the problem through flatmap:
.flatMap(new Function<XmlItemRawObject, ObservableSource<XmlItemRawObject>>() {
#Override
public ObservableSource<XmlItemRawObject> apply(XmlItemRawObject xmlItemRawObject) throws Exception {
Observable<XmlItemRawObject> test = Observable.create(emitter -> {
channelRepository.getItemByUniqueId(xmlItemRawObject.getGuid())
.subscribe(
item -> {
emitter.onComplete();
}, throwable -> {}, () -> Observable.just(xmlItemRawObject));
});
return test.defaultIfEmpty(xmlItemRawObject);
}})
Can't check an empty "subquery" result, make an if-else construct to pass on the xmlItemRawObject along the chain
I am working with the java rx library now, I am having problems with the internal calls with flatMap and map, for some reason the most internal call is not been done.
Besides, if I replace the flatMap with suscribe() in the internal call, (before the childrenItemsResponse.forEach code), this code is executed, bat the execution is not synchronous, I mean this call is done after the main flatMap execution finish.
This is my code:
override fun getSportList(dCSServiceContext: DCSServiceContext): Single<List<EntityBrowse>> {
return dCSService.get(dCSServiceContext).flatMap { item ->
val entityBrowseList = arrayListOf<EntityBrowse>()
val section = builSection(item?.firstOrNull()!!)
if (item.firstOrNull()?.collections?.get("NavItems")?.size!! > 0) {
dCSServiceContext.contentIds = item.firstOrNull()?.collections?.get("NavItems")
buildNavItems(dCSServiceContext).map { section ->
return#map section
}.map { items ->
section.items = items
return#map entityBrowseList
}
} else {
Single.just(entityBrowseList)
}
}
}
The problem is presented in the buildNavItems, method:
private fun buildNavItems(dCSServiceContext: DCSServiceContext): Single<MutableList<Item>> {
return dCSService.get(dCSServiceContext).map { itemsResponse ->
val items: MutableList<Item> = arrayListOf()
itemsResponse.forEach { item ->
val transformedItem = buildItem(item!!)
if (item?.collections?.get("NavItems") != null) {
dCSServiceContext.contentIds = item?.collections?.get("NavItems")
val childrenItems: MutableList<Item> = arrayListOf()
dCSService.get(dCSServiceContext).flatMap { childrenItemsResponse ->
childrenItemsResponse.forEach { childrenItem ->
val transformedChildrenItem = buildItem(childrenItem!!)
childrenItems.add(transformedChildrenItem)
}
val section = Section("", "", false,childrenItems )
val data = Data(section)
val children = Children(data)
transformedItem.children = children
items.add(transformedItem)
Single.just(items)
}
} else {
val transformedItem = buildItem(item!!)
items.add(transformedItem)
}
}
return#map items
//Single.just(items)
}
}
More specifically, the in the line code: dCSService.get(dCSServiceContext).flatMap { childrenItemsResponse ->
This code is never executed.
I am not sure about what could be the cause of the issue.
Thanks in advance!!
To activate an observer chain, there has to be a subscription. You don't subscribe to the observer chain that starts with dCSService.... flatMap() itself won't subscribe to its interior observables until the outer observable chain has been subscribed to.
i would like to implement a Pollingservice which calls a REST Api every nDelay Seconds and notify all subscribers if the data has been changed. Now i have a little problem with my code since it always returns a value to my Consumer, even if the data has not been changed.
private Observable<List<HueLight>> pollingLightsObservable = null;
public Observable<List<HueLight>> getPollingLightsObservable() {
if (pollingLightsObservable == null) {
pollingLightsObservable = Observable.fromCallable(
() -> LightManager
.getInstance(context)
.getLights()
.blockingSingle())
// .distinctUntilChanged( (l1, l1) -> !l1.equals(l2) )
.repeatWhen(o -> o.concatMap(v -> Observable.timer(1, TimeUnit.SECONDS)));
}
return pollingLightsObservable;
}
Enabling or using the distinctUntilChanged dont change anything. Doesnt matter if i put it before or after my repeatWhen.
Since my RetroFit Call returns an Observable, i have to use blockingSingle(). Using the Observable directly it leads into a return of "4, 8, 12, 16, .." items with this sample:
LightManager.getInstance(context).getLights()
.repeatWhen(o -> o.concatMap(v -> Observable.timer(1, TimeUnit.SECONDS)))
Currently i subscribe from different classes/activites with
this.lightChangeSubscriber = PollingManager
.getInstance(getContext())
.getPollingLightsObservable()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(hueLights -> {
{
Log.d(TAG, "Lights received successfully! Size=" + hueLights.size());
}
});
I would lovely avoid using interfaces and timer to create the polling. What would you recommend ?
what about using some custom filter?
public class FilterDuplicateHueConfig implements Predicate<HueConfig> {
private HueConfig lastVal;
#Override
public boolean test(HueConfig newVal) {
if(lastVal == null) {
lastVal = newVal;
return true;
}
... compare here the two values and return true/false appropriately...
}
}
I'm writing a simple test that is equivalent to:
Test fun testObservable() {
val returnedObservable = Observable.create(object : Observable.OnSubscribe<String> {
override fun call(t: Subscriber<in String>) {
t.onNext("hello")
t.onCompleted()
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
val result = returnedObservable.toBlocking().first()
assertEquals("hello", result)
}
The test blocks indefinitely on returnedObservable.toBlocking().first() when .observeOn(AndroidSchedulers.mainThread()) is present.
Is there a way to transform the observable to return the result?
The returnedObservable is returned from method call with .subscribeOn and .observeOn already applied so removing those is not an option.
I guess it is a bug mentioned here: https://github.com/ReactiveX/RxAndroid/issues/50
btw why don't you use RxKotlin?
You example will look much better:
val returnedObservable = observable<String> { subscriber ->
subscriber.onNext("hello")
subscriber.onCompleted()
}
.subscribeOn(Schedules.io())
.observeOn(AndroidSchedulers.mainThread())