I'm noob in RxJava but someday... ; )
What I have:
Receiving json with Event Planner Model
inside this model the are Tasks
I want to save this tasks into local db but also want add to them parent_id in case of relationship
Can someone with more experience take a look at this code:
private fun saveEventPlanners(eventPlanners: ArrayList<EventPlanner>) {
LogMgr.d(TAG, "saveEventPlanners() events:$eventPlanners")
compositeDisposable.add(wfmStorageDomain.saveEventPlanners(eventPlanners)
.subscribeOn(rxSchedulers.computation())
.observeOn(rxSchedulers.computation())
.subscribe({ saved ->
LogMgr.v(TAG, "event planners saved successfully, value: $saved")
val taskList = ArrayList<Task>()
eventPlanners.forEach { eventPlanner ->
if (eventPlanner.status == EventPlanner.EventPlannerStatus.NEW) {
FS.get().wfmComponent.getEventPlannerStatusChanger().updateEventPlannersStatus(eventPlanner.event_id!!, EventPlanner.EventPlannerStatus.RECEIVED)
}
(eventPlanner.tasks as ArrayList<Task>).forEach {
val task = Task()
task.id = it.id
task.status_id = Task.STATUS.NEW
task.name = it.name
task.end_scenario_id = it.end_scenario_id
task.start_scenario_id = it.start_scenario_id
task.isBind = it.isBind
task.parent_event_planner_id = eventPlanner.event_planner_id
taskList.add(task)
}
compositeDisposable.add(wfmStorageDomain.saveTasks(taskList)
.subscribeOn(rxSchedulers.computation())
.observeOn(rxSchedulers.computation())
.subscribe({
LogMgr.d(TAG, "tasks saved successfully = $it")
}, {
LogMgr.e(TAG, "Error while saving tasks", it)
}))
}
}, {
LogMgr.e(TAG, "Error while saving event planners", it)
}))
notifyObservers(eventPlanners)
}
I'm getting error in this function:
java.util.ConcurrentModificationException
at java.util.ArrayList$ArrayListIterator.next(ArrayList.java:573)
at com.raizlabs.android.dbflow.sql.saveable.ListModelSaver.saveAll(ListModelSaver.java:32)
at com.raizlabs.android.dbflow.sql.saveable.ListModelSaver.saveAll(ListModelSaver.java:19)
at com.raizlabs.android.dbflow.structure.ModelAdapter.saveAll(ModelAdapter.java:196)
at com.raizlabs.android.dbflow.rx2.structure.RXModelAdapter$3.call(RXModelAdapter.java:61)
at com.raizlabs.android.dbflow.rx2.structure.RXModelAdapter$3.call(RXModelAdapter.java:58)
at io.reactivex.internal.operators.completable.CompletableFromCallable.subscribeActual(CompletableFromCallable.java:35)
at io.reactivex.Completable.subscribe(Completable.java:1919)
at io.reactivex.internal.operators.completable.CompletableSubscribeOn$SubscribeOnObserver.run(CompletableSubscribeOn.java:64)
at io.reactivex.Scheduler$DisposeTask.run(Scheduler.java:579)
at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:66)
at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:57)
at java.util.concurrent.FutureTask.run(FutureTask.java:234)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:153)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:267)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1080)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:573)
at java.lang.Thread.run(Thread.java:841)
And this is how looks saving into db:
override fun saveTasks(tasks: ArrayList<Task>): Single<Boolean> {
LogMgr.d(TAG, "saveTasks() : $tasks")
return Single.create({ emitter ->
RXModelAdapter.from(Task::class.java)
.saveAll(tasks)
.subscribeOn(getSubscriptionScheduler())
.subscribe({
LogMgr.d(TAG, "saveTasks() onComplete")
emitter.onSuccess(true)
}, {
LogMgr.e(TAG, "saveTasks() onError ", it)
emitter.onError(it)
})
})
}
private fun getSubscriptionScheduler(): Scheduler {
return FS.get().commonComponent.rxSchedulers.get().sqlite()
}
The problem is this:
val taskList = ArrayList<Task>()
eventPlanners.forEach { eventPlanner ->
// ...
compositeDisposable.add(wfmStorageDomain.saveTasks(taskList)
// ...
}
You have a list which you keep adding from different eventPlanner items and submitting that partial list to the save task. If there are more than one item in eventPlanners this will result in the ConcurrentModificationException error. Either make taskList local to the forEach loop or move the saveTasks out of forEach.
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'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
I am trying to implement MVI Architecture in Android, but don't want to use Mosby Library. I want to learn the basics first.
I am building a sample app where when I press a button, text in the textview changes(initially the text is something else). Here is the code for MainActivity and MainPresenter.
class MainActivity : AppCompatActivity(), MainContract.View {
lateinit var mPresenter: MainContract.Presenter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mPresenter = MainPresenter()
mPresenter.attachPresenter(this)
bind()
}
#SuppressLint("CheckResult")
private fun bind() {
mPresenter.states().subscribe({ state ->
render(state)
}, {
Log.e("error", "Error is: ", it)
it.printStackTrace()
})
mPresenter.addIntents(intents())
}
override fun intents(): Observable<MainIntent> {
return Observable.merge(
initialIntent(),
clickIntent()
)
}
override fun render(state: MainViewState) {
btn_show.isEnabled = state.isEnabledButton
helloWorldTextView.text = state.message
loadingIndicator.visibility = if (state.isLoading) View.VISIBLE else View.GONE
}
private fun initialIntent(): Observable<MainIntent.InitialIntent> = Observable.just(MainIntent.InitialIntent)
private fun clickIntent(): Observable<MainIntent.ClickIntent> {
return btn_show.clicks().map { MainIntent.ClickIntent("Eureka") }
}
}
class MainPresenter : MainContract.Presenter {
private val intentsSubject: PublishSubject<MainIntent> = PublishSubject.create()
override fun states(): Observable<MainViewState> {
return statesObservable
}
private lateinit var view: MainContract.View
override fun attachPresenter(view: MainContract.View) {
this.view = view
}
#SuppressLint("CheckResult")
override fun addIntents(intents: Observable<MainIntent>) {
intents.subscribe(intentsSubject)
}
private val reducer =
BiFunction { previousState: MainViewState, result: MainResult ->
when (result) {
is MainResult.InitialResult.InFlight -> previousState.copy(
isLoading = true,
message = "Initial Result",
isEnabledButton = false
)
is MainResult.InitialResult.Success -> previousState.copy(
isLoading = true,
message = "Initial Success",
isEnabledButton = true
)
is MainResult.InitialResult.Error -> previousState.copy(
isLoading = false,
message = "Error Initially",
isEnabledButton = true
)
is MainResult.ClickedResult.Success -> previousState.copy(
isLoading = false,
message = System.currentTimeMillis().toString(),
isEnabledButton = true
)
is MainResult.ClickedResult.Error -> previousState.copy(
isLoading = false,
message = "Error Clicked",
isEnabledButton = true
)
is MainResult.ClickedResult.InFlight -> previousState.copy(
isLoading = true,
message = "Clicked In Flight",
isEnabledButton = false
)
}
}
private fun actionFromIntent(intent: MainIntent): MainAction {
if (intent is MainIntent.InitialIntent) {
return MainAction.InitialAction
} else if (intent is MainIntent.ClickIntent) {
return MainAction.ClickedAction("Hello")
} else {
return MainAction.InitialAction
}
}
private var actionProcessor: ObservableTransformer<MainAction, MainResult> = ObservableTransformer { actions ->
actions.publish { shared ->
Observable.merge<MainResult>(
shared.ofType(MainAction.InitialAction::class.java).compose(initialActionProcessor),
shared.ofType(MainAction.ClickedAction::class.java).compose(clickActionProcessor)
)
}
}
private val initialActionProcessor =
ObservableTransformer<MainAction.InitialAction, MainResult.InitialResult> { action: Observable<MainAction.InitialAction> ->
action.switchMap {
Observable.just("hello initially")
.map { MainResult.InitialResult.Success(it) }
.cast(MainResult.InitialResult::class.java)
.onErrorReturn { MainResult.InitialResult.Error(it.message!!) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.startWith { MainResult.InitialResult.InFlight }
}
}
private val clickActionProcessor =
ObservableTransformer<MainAction.ClickedAction, MainResult.ClickedResult> { action: Observable<MainAction.ClickedAction> ->
Observable.just("Click").map { message ->
MainResult.ClickedResult.Success(message)
}.cast(MainResult.ClickedResult::class.java)
.onErrorReturn { MainResult.ClickedResult.Error("Error") }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.startWith { MainResult.ClickedResult.InFlight }
}
private val statesObservable: Observable<MainViewState> = compose()
private fun compose(): Observable<MainViewState> {
return intentsSubject
.map {
actionFromIntent(it)
}
.compose(actionProcessor)
.scan(MainViewState.idle(), reducer)
.distinctUntilChanged()
.replay(1)
.autoConnect(0)
}
}
Problem is that only the Inital event is fired and nothing else. The code doesn't respond to clicks, render is called only initially once.
Also, if I remove the startWith{} from the actionProcessors code responds to clicks, but only once. After that, nothing happens.
Does anyone see issue with the code? I have been trying to get my head around this problem for a while now.
My previous reply:
It's not straight answer to your question. But if you implement what's below, you probably won't have the problem you actually asked about and you'll have easier MVI solution.
You probably try to merge https://speakerdeck.com/jakewharton/the-state-of-managing-state-with-rxjava-devoxx-us-2017, http://hannesdorfmann.com/android/mosby3-mvi-1 and https://medium.com/#oldergod/managing-state-with-rxjava-b0798a6c5757 ideas.
Take a look here: https://proandroiddev.com/taming-state-in-android-with-elm-architecture-and-kotlin-part-1-566caae0f706 - it's simpler. Part 1 and 2 should be enough.
I tried the 1st approach and was repulsed by initial complexity. In 2nd approach you don't have Action, Intent, Result, but Msg instead. It's simpler to reason about.
There's also new MVI course - but haven't checked it yet.
Current approach:
I tried mentioned Elm Architecture, but it is not complete. There are at least 2 problems:
Only one request can get through queue at one moment. Some RxJava
should do the trick (groupBy with 2 streams: ui, background
probably).
parallel requests would update the same state, so you should differentiate DataStates inside your UiState. So different state for different part of UI.
Before writing actual fixes we realised, this is not the way to go ATM: announced Composables could do the MVI trick: smooth and precise data transition to specific parts of UI.
Disclaimer: moderator removed my answer which WAS actual answer. Even more, my answer moved to comment is cut down, which makes it look unfinished. That's why this post emerged once again. After you read it dear moderator, you can remove disclaimer, thanks :)
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 have this code:
compositeDisposable.add(RetrofitClient
.getApi()
.getData()
.flatMap(response -> {
Data data;
if (response.isSuccessful()) {
data = response.body();
//insert data to database
Database.getInstance(context)
.getDao()
.insert(data);
} else {
ResponseBody responseBody = response.errorBody();
if (responseBody != null) {
data = new Gson().fromJson(responseBody.charStream(), Data.class);
}
}
return Observable.just(data);
})
.onErrorResumeNext(throwable -> {
//get data from database
Data data = Database.getInstance(context).getDao().getData();
return Observable.just(data);
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.retryWhen(throwableObservable ->
throwableObservable.take(1).delay(1, TimeUnit.SECONDS))
.doOnTerminate(view::hideScreenLoader)
.subscribe(this::showData, throwable -> {
Toast.makeText(context,
throwable.getMessage(), Toast.LENGTH_LONG).show();
}));
I can't understand why sometimes users got this error: "Caused by java.lang.RuntimeException Can't toast on a thread that has not called Looper.prepare()".
Toast must called at main thread, thanks.
I found the decision in this topic: how to handle RxAndroid errors in the main thread;
Need to swap "retryWhen" and "observeOn", because retryWhen has "delay function", that switch to computation thread :)