Realm.firstFirstAsync().asObservable() isn't consistently working with RxJava.switchIfEmpty - realm

I am trying to create function which reads a object from realm and emit an empty observable if the object isn't found. The code below works to some degree because I can stop it with the debugger and see it hit the Observable.empty():
fun readFromRealm(id: String): Observable<Player> {
return realm.where(Player::class.java)
.equalTo("id", id)
.findFirstAsync()
.asObservable<Player>()
.filter { it.isLoaded }
.flatMap {
if (it.isValid)
Observable.just(it)
else
Observable.empty()
}
}
But when I try to use a switchIfEmpty on the Observable the code never emits defaultPlayer when it is not found in realm.
return readFromRealm(playerId)
.take(1)
.map{ // do something with emitted observable }
.switchIfEmpty(Observable.just(defaultPlayer)) // use this if no player found
The strange thing is that if I update the original method to include a first() prior to the flatMap :
fun readFromRealm(id: String): Observable<Player> {
return realm.where(Player::class.java)
.equalTo("id", id)
.findFirstAsync()
.asObservable<Player>()
.filter { it.isLoaded }
.first() // add first
.flatMap {
if (it.isValid)
Observable.just(it)
else
Observable.empty()
}
}
Everything starts working as expected, but I believe this version will kill auto updating because it will only capture the first result emitted after the filter.
I'm still trying to grok Realm and Rx so I'm probably doing something dumb.
EDIT: I have created a sample project which highlights the issue I'm seeing - https://github.com/donaldlittlepie/realm-async-issue

For reasons I don't totally understand. If you move take(1) just above the
flatMap and below the filter it should work correctly:
realm.where(Dog.class)
.equalTo("id", 0L)
.findFirstAsync()
.asObservable()
.cast(Dog.class)
.filter(new Func1<RealmObject, Boolean>() {
#Override
public Boolean call(RealmObject realmObject) {
return realmObject.isLoaded();
}
})
.take(1) // <== here
.flatMap(new Func1<Dog, Observable<Dog>>() {
#Override
public Observable<Dog> call(Dog realmObject) {
if (realmObject.isValid()) {
return Observable.just(realmObject);
} else {
return Observable.empty();
}
}
})
.map(new Func1<Dog, Dog>() {
#Override
public Dog call(Dog dog) {
dog.setName("mapped " + dog.getName());
return dog;
}
})
.switchIfEmpty(Observable.just(createDefaultDog()))
.subscribe(new Action1<Dog>() {
#Override
public void call(Dog dog) {
textView.setText(dog.getName());
}
}, new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
textView.setText(throwable.toString());
}
});
My best guess is that before, flatMap was called repeatedly, returning Observable.empty() multiple times. Perhaps that effects the Observable chain in some unexpected way.

Related

Multiple chained API calls to fetch data, but doOnNext of PublishSubject is never reached

I have a problem to understand a chained "RXJava-Retrofit" API call. I got inspired by this and implement this class named ObservationLoader to load the data from the API bucket per bucket. When the end of data is reached the API sends a endOfRecords=true:
public Observable<PageObject<Observation>> getAllObservationDataByRegion(long taxonKey,
String regionId) {
final PublishSubject<PageObject<Observation>> subject = PublishSubject.create();
return subject.doOnSubscribe(disposable -> {
this.getData(taxonKey, regionId, 0).subscribe(subject);
})
.doOnNext(observationPageObject -> {
if (observationPageObject.isEndOfRecords()) {
// -> list is completely loaded
subject.onComplete();
} else {
int nextOffset = observationPageObject.getOffset() + 1;
this.getData(taxonKey, regionId, null, nextOffset).subscribe(subject);
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
private Observable<PageObject<Observation>> getData(long id,
String regionId,
int offset) {
// Get your API response value
return this.api.getObservations(id, regionId, ObservationLoader.PAGE_LIMIT, offset);
}
In my Android fragment HomeFragment I subscribe to the ObservationLoader:
ObservationLoader loader = new ObservationLoader(this.getApi());
Observable<PageObject<Observation>> observable = loader
.getAllObservationDataByRegion(this.getSelectedSpecies(), this.getSelectedRegion());
observable.subscribe(new Observer<PageObject<Observation>>() {
#Override
public void onSubscribe(Disposable d) {
Log.i(TAG, "ON_SUBSCRIBE");
}
#Override
public void onNext(PageObject<Observation> observationPageObject) {
Log.i(TAG, "ON_NEXT");
}
#Override
public void onError(Throwable e) {
Log.i(TAG, "ERROR = " + e.getMessage());
}
#Override
public void onComplete() {
Log.i(TAG, "COMPLETED");
}
});
I can see that the onSubscribe() and doOnSubscribe() are called and even the getData() is reached. I assume the API is responding correctly (a previous attempt attempt with recursion worked fine). But I never reached the doOnNext function. The observer goes straight to onComplete() and no data is received. What could be the reason?
When doOnSubscribe runs, the doesn't see any consumers yet so if getData is synchronous, there won't be any first results to trigger further results. Also if getData ends, it will complete the setup so the next getData call in doOnNext will push to an already terminated subject, ingoring all data.
You'll need a differently organized feedback loop:
// we loop back the nextOffset, in a thread-safe manner
Subject<Integer> subject = PublishSubject.<Integer>create()
.toSerialized();
// bootstrap with 0 and keep open for more offsets
subject.mergeWith(Observable.just(0))
// get the data for the current offset
.concatMap(nextOffset -> getData(taxonKey, regionId, nextOffset)
.subscribeOn(Schedulers.io())
)
// if the response is end of records, stop
.takeWhile(observationPageObject -> !observationPageObject.isEndOfRecords())
// otherwise not end of records, feedback the new offset
.doOnNext(observationPageObject ->
subject.onNext(observationPageObject.getOffset() + 1)
)
// get the data on the main thread
.observeOn(AndroidSchedulers.mainThread());

How to get data from Flowable to another Flowable?

I have 2 Flowables (one which is giving me VelocityNed items, and other which I written to consume items from first one); the thing is I don't know how to make the second one right, since I still not feel sure with RxJava
my Flowable code:
private Flowable<Float> getIAS(Flowable<VelocityNed> velocityNed) {
Flowable<Float> flowable = Flowable.create(emitter->{
velocityNed.subscribeWith(new DisposableSubscriber<VelocityNed>() {
#Override public void onNext(VelocityNed v) {
float valueToEmit = (float)Math.sqrt(Math.pow(v.getDownMS(),2)+Math.pow(v.getEastMS(),2)+Math.pow(v.getNorthMS(),2));
//how to emit this
}
#Override public void onError(Throwable t) {
t.printStackTrace();
}
#Override public void onComplete() {
emitter.onComplete();
this.dispose();
}
});
}, BackpressureStrategy.BUFFER);
return flowable;
}
You don't need to create a Flowable manually just to transform the emissions. You can do originalFlowable.map(element -> { transform element however you want }).
In your case it would be something like:
Flowable<Float> flowable = velocityNed.map(v -> {
(float)Math.sqrt(Math.pow(v.getDownMS(),2)+Math.pow(v.getEastMS(),2)+Math.pow(v.getNorthMS(),2));
})

type 'Future<dynamic>' is not a subtype of type 'List<CloseObservations>'

I'm very new to dart and i am countering an issue with a function that suppose return a List<CloseObservations> and not a Future<dynamic> and i couldn't figure out why my function do not return the correct type.
This is my function :
getData(BuildContext context) async {
List<CloseObservations> closeObservationData = new List();
WorkSiteState state = BlocProvider.of<WorkSiteBloc>(context).state;
List<Stage> stages = state.workSites[state.currentIndex].stages;
for (Stage stage in stages) {
String stageName = stage.name;
int closeNumber;
try {
for (Plan plan in stage.plans) {
for (Observation observation in plan.observations) {
if (!observation.open) {
closeNumber++;
}
}
}
} finally {
closeObservationData.add(CloseObservations(stageName, closeNumber));
}
}
return closeObservationData;
}
I hope the explanation of my problem is clear, if not tell me.
Thank you for your help !
You should explicitly specify return type of your function:
Future<List<CloseObservations>> getData(BuildContext context) async {
...
Async functions always return Future (there's also FutureOr, but you shouldn't return it - that's bad practice). If you don't need it to return Future, make it synchronous by removing async keyword and changing return type to List.

After using Angular2+ RouteReuseStrategy, how to call a method in return to cache components to update part?

I have a list page, and click a column to enter the details page. Edit and return. Because using RouteReuseStrategy, it can maintain the list page of the scene remains the same. But I'd like to partially update, However, I don't know how to trigger it.
Here is my RouteReuseStrategy service and most of the same.
export class SimpleReuseStrategy implements RouteReuseStrategy {
_cacheRouters: { [key: string]: any } = {};
shouldDetach(route: ActivatedRouteSnapshot): boolean {
return true;
}
store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
this._cacheRouters[route.routeConfig.path] = {
snapshot: route,
handle: handle
};
}
shouldAttach(route: ActivatedRouteSnapshot): boolean {
return !!this._cacheRouters[route.routeConfig.path];
}
retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
return this._cacheRouters[route.routeConfig.path].handle;
}
shouldReuseRoute(future: ActivatedRouteSnapshot, curr:
ActivatedRouteSnapshot): boolean {
return future.routeConfig === curr.routeConfig;
}
}
In your list page component you can listen to route changes:
Detect if the navigation start came from the detail route i.e. /detail/1
if so, get the 'id' param of the route /detail/1
replace that element in the list
// List component
ngOnInit(): void {
this.subscription = this.router.events.subscribe((event) => {
if (event instanceof NavigationStart) {
// checkIfDetailPage and store ID
}
if (event instanceof NavigationEnd) {
// checkIfThisPage and get the ID
// replace element with ID
}
});
}
An alternative approach for detecting something changed (instead of observing the route changes) you could implement Service to which your list component subscribes and your detail component notifies.

TornadoFX with TestFX close the View after every TestCase

I am trying to test a basic loginscreen (created using tornadofx) with the testfx framework.
I have added 3 test cases which runs fine but the problem is they use the previous stage rather than creating a new one. I want the testcases to run independently.
I am testing a View() and not an App(). If I use MyMainApp().start(stage) and then MyMainApp().stop(), I get the required behaviour.
But how to do this for Views and Fragments.
Below is the code:
class LoginScreenFeatureTest : ApplicationTest() {
override fun init() {
FxToolkit.registerStage { Stage() }
}
override fun start(stage: Stage) {
LoginScreen().openWindow()
//MyMainApp().start(stage)
}
override fun stop() {
FxToolkit.cleanupStages()
//FxToolkit.toolkitContext().registeredStage.close()
//MyMainApp().stop()
}
#Test fun should_contain_button() {
// expect:
verifyThat("#submitBut", hasText("SUBMIT"))
}
#Test fun should_click_on_button_and_pass_login() {
//init
//Why do I always need to erase text. I want a new stage for every test case.
clickOn("#username").eraseText(10).write("validUser")
clickOn("#password").eraseText(10).write("validPwd")
clickOn("#orgId").eraseText(10).write("validOrg")
// when:
clickOn("#submitBut")
// then:
//verify success
}
#Test fun should_click_on_button_and_fail_login() {
//init
clickOn("#username").eraseText(10).write("anyuser")
clickOn("#password").eraseText(10).write("anypwd")
clickOn("#orgId").eraseText(10).write("anyorg")
// when:
clickOn("#submitBut")
// then:
//verify fail
}
}
You can add property which you can edit at any time at you App() class.
class MyMainApp: App() {
override val primaryView: KClass<out View> = primaryViewMyApp
companion object {
var primaryViewMyApp: KClass<out View> = MyMainAppView::class
}
init {
importStylesheet(<your stylesheet>)
}
override fun start(stage: Stage) {
super.start(stage)
}
override fun stop() {
super.stop()
}
}
and in the test you can than use any view you want to use. I didnt try to implement something for Fragment so far...
override fun init() {
FxToolkit.registerStage { Stage() }
}
override fun start(stage: Stage) {
MyMainApp.primaryViewGoApp = <your view>::class
MyMainApp().start(stage)
}
override fun stop() {
FxToolkit.cleanupStages()
MyMainApp().stop()
}

Resources