Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add RxJava support #1710

Merged
merged 1 commit into from
Dec 17, 2015
Merged

Add RxJava support #1710

merged 1 commit into from
Dec 17, 2015

Conversation

cmelchior
Copy link
Contributor

This PR fixes #865

This PR is a feature branch for collecting all RxJava sub-branches before merging. Right now the roadmap is this:

1 + 2 is required before merging to main branch, 3 would be nice to have, 4 + 5 will probably take a little longer and will tie into handover mechanisms already considered elsewhere.


API: (for step 1)

Realm.asObservable();
RealmObject.asObservable();
RealmResults.asObservable();
DynamicRealm.asObservable();
DynamicRealmObject.asObservable();
RealmConfiguration.Builder.rxFactory(RxObservableFactory factory);

Note that all Rx classes are in a new package io.realm.rx. Should we move these into io.realm as that currently holds all our public API? It doesn't conflict with the proposal of adding io.realm.android (as RxJava is not Android only), but something to consider when adding future classes/functionality.


  • Finalize API proposal in [Design] RxJava support #1651. Especially if RxObservableFactory should move to its own dependency. Conclusion: Keep factory classes in repo for now. Other questions:
    1. We cannot have rx classes in a io.realm.rx without making BaseRealm public, is having them in io.realm OK? Otherwise should we make BaseRealm public but perhaps exclude it from JavaDoc? Answer: Added methods explicitly naming Realm/DynamicRealm to work around this.
    2. Should we expose observable methods on RealmQuery? YES: See other PR
    3. RealmObject is now RealmObject<E extends RealmObject<E>> which is slightly painful if you want type safe observables for RealmObjects. You need to do public class Person extends RealmObject<Person>. It is not mandatory though. Answer: YES, IntelliJ will give you warning if no generic parameter is given, but otherwise it works fine in all other cases, i.e. only RxJava users will notice it.
  • Fix changelistener bug ChangeListener not triggered on newly created RealmObject #1884
  • Fix changeListeners not triggering for synchronous queries on RealmResults/RealmObjects ( Change listener not being triggered on realm updates #1676).
  • Use copyFromRealm for InMemoryObservableFactory ( Adds Realm.copyFromRealm() #1849 )
  • Investigate the possibility to use a custom RealmScheduler to facilitate moving across threads. Just like our current async API does now. Conclusion: Not possible as the scheduler does not have access to the parameters being parsed around. With auto-closable Realms it will be possible, or wait for freeze() semantics.
  • More examples of Observables with 'gotchas' have to be added.
  • Throw proper error message if calling observable() and RxJava is not on the classpath

@@ -81,7 +82,7 @@
*/

@RealmClass
public abstract class RealmObject {
public abstract class RealmObject<E extends RealmObject> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ouch :( That's not pretty...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it is horrible. Fortunately it is only required for RealmObjects as both RealmResults and RealmList has the type information.

I tried different versions of generics black magic, but AFAIK it isn't possible for an abstract class to return a subclass type without it being explicitly defined somewhere (either as a generic like here, or as input parameter to a method)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It kind of makes me reconsider RealmObservable.from() and allow users to throw anything at it, a bit like http://reactivex.io/RxJava/javadoc/rx/Observable.html#from(java.util.concurrent.Future)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the pattern is not unknown from RxJava itself: Observable.from(), Observable.just(), but it really really break up the flow when you are coding. I tried it when working on the examples.

Upside is that you don't actually need the generic, but if you don't add it you just get Object as the return type in the Observable chain.

I have been contemplating if we should add an explicit ListenerInterface, but that just seems like unnecessary boilerplate

@cmelchior cmelchior mentioned this pull request Dec 2, 2015
final RealmChangeListener listener = new RealmChangeListener() {
@Override
public void onChange() {
subscriber.onNext(realm);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this should be checking subscriber.isUnsubscribed() before calling onNext to follow the Rx contract. Same with all the other from methods.

More info:
http://ryanharter.com/blog/2015/07/07/wrapping-existing-libraries-with-rxjava/
https://www.reddit.com/r/androiddev/comments/3vebo3/resubmission_respect_the_observable_contract_when/

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @austynmahoney
Thanks for the pointers. However I added the subscriber.add(...) below to prevent that exact scenario. That should unregister the listener when unsubscribing which means it shouldn't be needed to check for subscriber.isUnsubscribed in the callback. I won't rule out I made a mistake somewhere though, so i'll go over it again :)

Source: http://stackoverflow.com/questions/26695125/how-to-get-notified-of-a-observers-unsubscribe-action-in-a-custom-observable-in

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd still vote for following the contract and adding the check anyways. If some code changes down the road, or the unsubscribe happens on another scheduler, it could end up breaking the contract.

@cmelchior
Copy link
Contributor Author

Ready for review

@realm/java
@donnfelker

@cmelchior cmelchior mentioned this pull request Dec 14, 2015
@cmelchior cmelchior self-assigned this Dec 15, 2015
@@ -0,0 +1,288 @@
package io.realm;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Standard header?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added

@kneth
Copy link
Member

kneth commented Dec 16, 2015

I see no tests for RealmQuery and ReamList. We should check if the correct exception is thrown - just to ensure that the JavaDoc is consistent with the behaviour.

@cmelchior
Copy link
Contributor Author

We don't have to write any tests for them yet, as the API is not exposed, so there is nothing that can crash. I added from(RealmQuery) and from(RealmList) to make the interface future-proof and avoid having to introduce breaking changes to it later.

i.e. without the methods RealmQuery.asObservable() and RealmList.asObservable() those methods are currently no-ops.

* Creates an Observable for a {@link Realm}. It should emit the initial state of the Realm when subscribed to and
* on each subsequent update of the Realm.
*
* Realm observables are hot observables as Realms automatically are kept up to date.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Realm observables are hot observables as Realms automatically are kept up to date.

as Realms are automatically kept up to date.

same for the below repetitions

@cmelchior
Copy link
Contributor Author

Issues fixed @realm/java

@kneth
Copy link
Member

kneth commented Dec 17, 2015

👍 (you have responded to my comments)

realm.where(AllTypes.class).findFirstAsync().<AllTypes>asObservable().subscribe(new Action1<AllTypes>() {
@Override
public void call(AllTypes rxObject) {
subscriberCalled.addAndGet(1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be subscriberCalled.incrementAndGet()

@emanuelez
Copy link
Contributor

👍 apart from very minor comments


// Immediately call onNext with the current value, as due to Realms auto-update, it will be the latest
// value.
subscriber.onNext(results);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can also check subscriber.isUnsubscribed() here as well or drop the if on line 128 depending on how eagerly you want to handle the small removeChangeListener/unsubscription window.

@emanuelez emanuelez deleted the feature-rxjava branch November 25, 2016 08:04
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Mar 16, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

RxJava support
6 participants