Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Build Your Custom Observable/Operator

Build Your Custom Observable/Operator

In this talk, I am explaining how to model a stream and how to implement custom observable/operator, taking RxNearby (https://github.com/hkurokawa/RxNearby) as an example.

There are some pitfalls in developing your own Observable or Operator and this talk covers that.

Links:
- Pitfalls of operator implementations (part 1)
http://akarnokd.blogspot.jp/2015/05/pitfalls-of-operator-implementations.html

Hiroshi Kurokawa

February 18, 2016
Tweet

More Decks by Hiroshi Kurokawa

Other Decks in Technology

Transcript

  1. When you need custom Observable/Operator? // Retrofit service.users() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread())

    .subscribe(users -> { adapter.setItems(users); }); // RxBinding RxView.scrollChangeEvents(listView) .subscribe(event -> { int dy = event.scrollY() - event.oldScrollY(); });
  2. When you need custom Observable/Operator? // Retrofit service.users() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread())

    .subscribe(users -> { adapter.setItems(users); }); // RxBinding RxView.scrollChangeEvents(listView) .subscribe(event -> { int dy = event.scrollY() - event.oldScrollY(); }); When you want to model something as a stream!
  3. When you need custom Observable/Operator? // Retrofit service.users() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread())

    .subscribe(users -> { adapter.setItems(users); }); // RxBinding RxView.scrollChangeEvents(listView) .subscribe(event -> { int dy = event.scrollY() - event.oldScrollY(); }); When you want to model something as a stream!
  4. When you need custom Observable/Operator? // Retrofit service.users() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread())

    .subscribe(users -> { adapter.setItems(users); }); // RxBinding RxView.scrollChangeEvents(listView) .subscribe(event -> { int dy = event.scrollY() - event.oldScrollY(); }); When you want to model something as a stream!
  5. When you need custom Observable/Operator? // Retrofit service.users() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread())

    .subscribe(users -> { adapter.setItems(users); }); // RxBinding RxView.scrollChangeEvents(listView) .subscribe(event -> { int dy = event.scrollY() - event.oldScrollY(); }); When you want to model something as a stream!
  6. Nearby Messaging API • Subscribe messages from other Android devices


    Modelled as Observable • Publish messages to other Android devices
  7. Nearby Messaging API • Subscribe messages from other Android devices


    Modelled as Observable • Publish messages to other Android devices
 Modelled as Operator Operator
  8. Nearby Messaging API • Subscribe messages from other Android devices


    Modelled as Observable • Publish messages to other Android devices
 Modelled as Operator 9JCV!1RGTCVQT!
  9. Why Operator? • When and how do you want a

    message is published? - Publish when user clicks a button - Publish when the app loads data from local DB or a server • Why don’t you handle such trigger as an Observable?
  10. What is Observable? What is Operator? • Observable is something

    to emit a sequence of events to the given Subscriber
  11. What is Observable? What is Operator? • Observable is something

    to emit a sequence of events to the given Subscriber - From implementation points of view, we create an OnSubscribe instead of Observable itself Observable.create(new OnSubscribe() { … })
  12. What is Observable? What is Operator? • Observable is something

    to emit a sequence of events to the given Subscriber - From implementation points of view, we create an OnSubscribe instead of Observable itself • Operator converts an Observable to another Observable Observable.create(new OnSubscribe() { … })
  13. What is Observable? What is Operator? • Observable is something

    to emit a sequence of events to the given Subscriber - From implementation points of view, we create an OnSubscribe instead of Observable itself • Operator converts an Observable to another Observable - From implementation points of view, we implement a method to return a subscriber from the given subscriber Observable.create(new OnSubscribe() { … })
  14. What is Observable? What is Operator? • Observable is something

    to emit a sequence of events to the given Subscriber - From implementation points of view, we create an OnSubscribe instead of Observable itself • Operator converts an Observable to another Observable - From implementation points of view, we implement a method to return a subscriber from the given subscriber Observable.create(new OnSubscribe() { … }) Subscriber is the core of RxJava
  15. OnSubscribe Example:
 range() @Override public void call(Subscriber<? super Integer> subscriber)

    { for (int i = start; i < start + count; i++) { if (subscriber.isUnsubscribed()) { return; } subscriber.onNext(i); } if (!subscriber.isUnsubscribed()) { subscriber.onCompleted(); } }
  16. How OnSubscribe works Observable.range(1, 5) .subscribe( i -> Log.i(TAG, "Output:

    " + i), throwable -> Log.e(TAG, "Error: ", throwable), () -> Log.i(TAG, “Completed.") );
  17. How OnSubscribe works Observable.range(1, 5) .subscribe( i -> Log.i(TAG, "Output:

    " + i), throwable -> Log.e(TAG, "Error: ", throwable), () -> Log.i(TAG, “Completed.") );
  18. How OnSubscribe works Observable.range(1, 5) .subscribe( i -> Log.i(TAG, "Output:

    " + i), throwable -> Log.e(TAG, "Error: ", throwable), () -> Log.i(TAG, “Completed.") ); @Override public void call(Subscriber<? super Integer> subscriber) { for (int i = start; i < start + count; i++) { if (subscriber.isUnsubscribed()) { return; } subscriber.onNext(i); } if (!subscriber.isUnsubscribed()) { subscriber.onCompleted(); } }
  19. How OnSubscribe works Observable.range(1, 5) .subscribe( i -> Log.i(TAG, "Output:

    " + 1), throwable -> Log.e(TAG, "Error: ", throwable), () -> Log.i(TAG, “Completed.") ); @Override public void call(Subscriber<? super Integer> subscriber) { for (int i = start; i < start + count; i++) { if (subscriber.isUnsubscribed()) { return; } subscriber.onNext(1); } if (!subscriber.isUnsubscribed()) { subscriber.onCompleted(); } }
  20. How OnSubscribe works Observable.range(1, 5) .subscribe( i -> Log.i(TAG, "Output:

    " + 2), throwable -> Log.e(TAG, "Error: ", throwable), () -> Log.i(TAG, “Completed.") ); @Override public void call(Subscriber<? super Integer> subscriber) { for (int i = start; i < start + count; i++) { if (subscriber.isUnsubscribed()) { return; } subscriber.onNext(2); } if (!subscriber.isUnsubscribed()) { subscriber.onCompleted(); } }
  21. How OnSubscribe works Observable.range(1, 5) .subscribe( i -> Log.i(TAG, "Output:

    " + 3), throwable -> Log.e(TAG, "Error: ", throwable), () -> Log.i(TAG, “Completed.") ); @Override public void call(Subscriber<? super Integer> subscriber) { for (int i = start; i < start + count; i++) { if (subscriber.isUnsubscribed()) { return; } subscriber.onNext(3); } if (!subscriber.isUnsubscribed()) { subscriber.onCompleted(); } }
  22. How OnSubscribe works Observable.range(1, 5) .subscribe( i -> Log.i(TAG, "Output:

    " + 4), throwable -> Log.e(TAG, "Error: ", throwable), () -> Log.i(TAG, “Completed.") ); @Override public void call(Subscriber<? super Integer> subscriber) { for (int i = start; i < start + count; i++) { if (subscriber.isUnsubscribed()) { return; } subscriber.onNext(4); } if (!subscriber.isUnsubscribed()) { subscriber.onCompleted(); } }
  23. How OnSubscribe works Observable.range(1, 5) .subscribe( i -> Log.i(TAG, "Output:

    " + 5), throwable -> Log.e(TAG, "Error: ", throwable), () -> Log.i(TAG, “Completed.") ); @Override public void call(Subscriber<? super Integer> subscriber) { for (int i = start; i < start + count; i++) { if (subscriber.isUnsubscribed()) { return; } subscriber.onNext(5); } if (!subscriber.isUnsubscribed()) { subscriber.onCompleted(); } }
  24. How OnSubscribe works Observable.range(1, 5) .subscribe( i -> Log.i(TAG, "Output:

    " + i), throwable -> Log.e(TAG, "Error: ", throwable), () -> Log.i(TAG, “Completed.") ); @Override public void call(Subscriber<? super Integer> subscriber) { for (int i = start; i < start + count; i++) { if (subscriber.isUnsubscribed()) { return; } subscriber.onNext(i); } if (!subscriber.isUnsubscribed()) { subscriber.onCompleted(); } }
  25. Operator Example: map() @Override public Subscriber<? super Integer> call(Subscriber<? super

    Integer> child) { return new Subscriber<Integer>() { @Override public void onCompleted() { child.onCompleted(); } @Override public void onError(Throwable e) { child.onError(e); } @Override public void onNext(Integer i) { try { child.onNext(transformer.call(i)); } catch (Throwable e) { Exceptions.throwOrReport(e, this, i); } } }; }
  26. Operator Example: map() @Override public Subscriber<? super Integer> call(Subscriber<? super

    Integer> child) { return new Subscriber<Integer>() { @Override public void onCompleted() { child.onCompleted(); } @Override public void onError(Throwable e) { child.onError(e); } @Override public void onNext(Integer i) { try { child.onNext(transformer.call(i)); } catch (Throwable e) { Exceptions.throwOrReport(e, this, i); } } }; }
  27. How Operator works Observable.range(1, 5) .map(i -> i * 2)

    .subscribe( i -> Log.i(TAG, "Output: " + i), throwable -> Log.e(TAG, "Error: ", throwable), () -> Log.i(TAG, "Completed.") );
  28. How Operator works Observable.range(1, 5) .lift(new OperatorMap(i -> i*2)) .subscribe(

    i -> Log.i(TAG, "Output: " + i), throwable -> Log.e(TAG, "Error: ", throwable), () -> Log.i(TAG, "Completed.") );
  29. How Operator works Observable.range(1, 5) .lift(new OperatorMap(i -> i*2)) .subscribe(

    i -> Log.i(TAG, "Output: " + i), throwable -> Log.e(TAG, "Error: ", throwable), () -> Log.i(TAG, "Completed.") );
  30. How Operator works Observable.range(1, 5) .lift(new OperatorMap(i -> i*2)) .subscribe(

    i -> Log.i(TAG, "Output: " + i), throwable -> Log.e(TAG, "Error: ", throwable), () -> Log.i(TAG, "Completed.") );
  31. How Operator works Observable.range(1, 5) .lift(new OperatorMap(i -> i*2)) .subscribe(

    i -> Log.i(TAG, "Output: " + i), throwable -> Log.e(TAG, "Error: ", throwable), () -> Log.i(TAG, "Completed.") ); public final <Integer> Observable<Integer> lift(final OperatorMap operator) { return new Observable<>(new Observable.OnSubscribe<Integer>() { @Override public void call(Subscriber<? super Integer> o) { // new Subscriber created and being subscribed with so 'onStart' it Subscriber<? super Integer> st = operator.call(o); st.onStart(); onSubscribe.call(st); } }); }
  32. How Operator works Observable.range(1, 5) .lift(new OperatorMap(i -> i*2)) .subscribe(

    i -> Log.i(TAG, "Output: " + i), throwable -> Log.e(TAG, "Error: ", throwable), () -> Log.i(TAG, "Completed.") ); public final <Integer> Observable<Integer> lift(final OperatorMap operator) { return new Observable<>(new Observable.OnSubscribe<Integer>() { @Override public void call(Subscriber<? super Integer> o) { // new Subscriber created and being subscribed with so 'onStart' it Subscriber<? super Integer> st = operator.call(o); st.onStart(); onSubscribe.call(st); } }); }
  33. How Operator works Observable.range(1, 5) .lift(new OperatorMap(i -> i*2)) .subscribe(

    i -> Log.i(TAG, "Output: " + i), throwable -> Log.e(TAG, "Error: ", throwable), () -> Log.i(TAG, "Completed.") ); Subscriber<? super Integer> st = operator.call(o); st.onStart(); onSubscribe.call(st);
  34. How Operator works Observable.range(1, 5) .lift(new OperatorMap(i -> i*2)) .subscribe(

    i -> Log.i(TAG, "Output: " + i), throwable -> Log.e(TAG, "Error: ", throwable), () -> Log.i(TAG, "Completed.") ); Subscriber<? super Integer> st = operator.call(o); st.onStart(); onSubscribe.call(st); child subscriber
  35. How Operator works Observable.range(1, 5) .lift(new OperatorMap(i -> i*2)) .subscribe(

    i -> Log.i(TAG, "Output: " + i), throwable -> Log.e(TAG, "Error: ", throwable), () -> Log.i(TAG, "Completed.") ); Subscriber<? super Integer> st = operator.call(o); st.onStart(); onSubscribe.call(st); child subscriber new subscriber
  36. How Operator works Observable.range(1, 5) .lift(new OperatorMap(i -> i*2)) .subscribe(

    i -> Log.i(TAG, "Output: " + i), throwable -> Log.e(TAG, "Error: ", throwable), () -> Log.i(TAG, "Completed.") ); Subscriber<? super Integer> st = operator.call(o); st.onStart(); onSubscribe.call(st);
  37. How Operator works Observable.range(1, 5) .lift(new OperatorMap(i -> i*2)) .subscribe(

    i -> Log.i(TAG, "Output: " + i), throwable -> Log.e(TAG, "Error: ", throwable), () -> Log.i(TAG, "Completed.") ); Subscriber<? super Integer> st = operator.call(o); st.onStart(); onSubscribe.call(st);
  38. How Operator works Observable.range(1, 5) .lift(new OperatorMap(i -> i*2)) .subscribe(

    i -> Log.i(TAG, "Output: " + i), throwable -> Log.e(TAG, "Error: ", throwable), () -> Log.i(TAG, "Completed.") ); @Override public void call(Subscriber<? super Integer> subscriber) { for (int i = start; i < start + count; i++) { if (subscriber.isUnsubscribed()) { return; } subscriber.onNext(i); } if (!subscriber.isUnsubscribed()) { subscriber.onCompleted(); } }
  39. How Operator works Observable.range(1, 5) .lift(new OperatorMap(i -> i*2)) .subscribe(

    i -> Log.i(TAG, "Output: " + i), throwable -> Log.e(TAG, "Error: ", throwable), () -> Log.i(TAG, "Completed.") ); @Override public void call(Subscriber<? super Integer> subscriber) { for (int i = start; i < start + count; i++) { if (subscriber.isUnsubscribed()) { return; } subscriber.onNext(i); } if (!subscriber.isUnsubscribed()) { subscriber.onCompleted(); } }
  40. How Operator works Observable.range(1, 5) .lift(new OperatorMap(i -> i*2)) .subscribe(

    i -> Log.i(TAG, "Output: " + i), throwable -> Log.e(TAG, "Error: ", throwable), () -> Log.i(TAG, "Completed.") ); @Override public void call(Subscriber<? super Integer> subscriber) { … subscriber.onNext(i); … }
  41. How Operator works Observable.range(1, 5) .lift(new OperatorMap(i -> i*2)) .subscribe(

    i -> Log.i(TAG, "Output: " + i), throwable -> Log.e(TAG, "Error: ", throwable), () -> Log.i(TAG, "Completed.") ); @Override public void call(Subscriber<? super Integer> subscriber) { … subscriber.onNext(i); … } @Override public void onNext(Integer i) { child.onNext(transformer.call(i)); }
  42. How Operator works Observable.range(1, 5) .lift(new OperatorMap(i -> i*2)) .subscribe(

    i -> Log.i(TAG, "Output: " + i), throwable -> Log.e(TAG, "Error: ", throwable), () -> Log.i(TAG, "Completed.") ); @Override public void call(Subscriber<? super Integer> subscriber) { … subscriber.onNext(i); … } @Override public void onNext(Integer i) { child.onNext(transformer.call(i)); }
  43. How Operator works • Operator defines how to create a

    new parent subscriber from the given child subscriber • When subscribed, it invokes its onSubscribe with that new parent subscriber • When the operators are chained, the onSubscribe invocations are also chained from child to parent • The onNext invocations are chained from parent to child
  44. Nearby Messages API • Subscribe messages from other Android devices


    Modelled as Observable • Publish messages to other Android devices
 Modelled as Operator
  45. Nearby Messages API • Subscribe messages from other Android devices


    Modelled as Observable • Publish messages to other Android devices
 Modelled as Operator
  46. MessageSubscribeOnSubscribe Nearby.Messages .subscribe(apiClient, new MessageListener() { @Override public void onFound(Message

    message) { if (!subscriber.isUnsubscribed()) { subscriber.onNext(message); } } }) .setResultCallback(callback); apiClient.connect();
  47. MessageSubscribeOnSubscribe Nearby.Messages .subscribe(apiClient, new MessageListener() { @Override public void onFound(Message

    message) { if (!subscriber.isUnsubscribed()) { subscriber.onNext(message); } } }) .setResultCallback(status -> { if (!status.isSuccess() && !subscriber.isUnsubscribed()) { subscriber.onError(new ApiStatusException(status)); } }); apiClient.connect();
  48. subscriber.add(new Subscription() { @Override public void unsubscribe() { } @Override

    public boolean isUnsubscribed() { return apiClient.isConnected(); } }); MessageSubscribeOnSubscribe apiClient.unsubscribe(listener, callback); apiClient.disconnect();
  49. MessageSubscribeOnSubscribe @Override public void call(final Subscriber<? super Message> subscriber) {

    final MessageListener listener = new MessageListener() { @Override public void onFound(Message message) { if (!subscriber.isUnsubscribed()) { subscriber.onNext(message); } } }; final ApiClientWrapper.ResultHandler handler = new ApiClientWrapper.ResultHandler() { @Override public void onSuccess() { } @Override public void onError(Status status) { if (!subscriber.isUnsubscribed()) { subscriber.onError(new ApiStatusException(status)); } } }; apiClient.subscribe(listener, handler); subscriber.add(new Subscription() { @Override public void unsubscribe() { apiClient.unsubscribe(listener, handler); apiClient.disconnect(); } @Override public boolean isUnsubscribed() { return apiClient.isConnected(); } }); apiClient.connect(); }
  50. Takeaways • Basically do not do anything until it is

    subscribed • Make sure any resources is released when unsubscribed • To make it back-pressure aware, use Subscriber#setProducer() • The thread of OnSubscribe#call() can be specified with subscribeOn() operator • There is a contract. See http://reactivex.io/ documentation/contract.html
  51. Nearby Messages API • Subscribe messages from other Android devices


    Modelled as Observable • Publish messages to other Android devices
 Modelled as Operator
  52. MessagePublishOperator @Override public Subscriber<? super Message> call(Subscriber<? super PublishResult> child)

    { final ApiClientWrapper apiClient = new ApiClientWrapper(this.context); return new Subscriber<Message>(child) { @Override public void onNext(Message message) { Nearby.Messages.publish(apiClient, message).setResultCallback(status -> { if (status.isSuccess()) { child.onNext(new PublishResult(message)); } else { child.onError(new ApiStatusException(status)); } }); } @Override public void onCompleted() { child.onCompleted(); } @Override public void onError(Throwable e) { child.onError(e); } }; }
  53. MessagePublishOperator @Override public Subscriber<? super Message> call(Subscriber<? super PublishResult> child)

    { final ApiClientWrapper apiClient = new ApiClientWrapper(this.context); return new Subscriber<Message>(child) { @Override public void onNext(Message message) { Nearby.Messages.publish(apiClient, message).setResultCallback(status -> { if (status.isSuccess()) { child.onNext(new PublishResult(message)); } else { child.onError(new ApiStatusException(status)); } }); } @Override public void onCompleted() { child.onCompleted(); } @Override public void onError(Throwable e) { child.onError(e); } }; }
  54. MessagePublishOperator final ApiClientWrapper apiClient = new ApiClientWrapper(this.context); return new Subscriber<Message>(child)

    { @Override public void onNext(Message message) { Nearby.Messages.publish(apiClient, message).setResultCallback(status -> { if (status.isSuccess()) { child.onNext(new PublishResult(message)); } else { child.onError(new ApiStatusException(status)); } }); } };
  55. MessagePublishOperator final ApiClientWrapper apiClient = new ApiClientWrapper(this.context); return new Subscriber<Message>(child)

    { @Override public void onNext(Message message) { Nearby.Messages.publish(apiClient, message).setResultCallback(status -> { if (status.isSuccess()) { child.onNext(new PublishResult(message)); } else { child.onError(new ApiStatusException(status)); } }); } };
  56. MessagePublishOperator final ApiClientWrapper apiClient = new ApiClientWrapper(this.context); return new Subscriber<Message>(child)

    { @Override public void onNext(Message message) { Nearby.Messages.publish(apiClient, message).setResultCallback(status -> { if (status.isSuccess()) { child.onNext(new PublishResult(message)); } else { child.onError(new ApiStatusException(status)); } }); } };
  57. MessagePublishOperator final ApiClientWrapper apiClient = new ApiClientWrapper(this.context); return new Subscriber<Message>(child)

    { @Override public void onNext(Message message) { Nearby.Messages.publish(apiClient, message).setResultCallback(status -> { if (status.isSuccess()) { child.onNext(new PublishResult(message)); } else { child.onError(new ApiStatusException(status)); } }); } };
  58. MessagePublishOperator final ApiClientWrapper apiClient = new ApiClientWrapper(this.context); return new Subscriber<Message>(child)

    { @Override public void onNext(Message message) { Nearby.Messages.publish(apiClient, message).setResultCallback(status -> { if (status.isSuccess()) { child.onNext(new PublishResult(message)); } else { child.onError(new ApiStatusException(status)); } }); } };
  59. MessagePublishOperator final ApiClientWrapper apiClient = new ApiClientWrapper(this.context); return new Subscriber<Message>(child)

    { @Override public void onNext(Message message) { Nearby.Messages.publish(apiClient, message).setResultCallback(status -> { if (status.isSuccess()) { child.onNext(new PublishResult(message)); } else { child.onError(new ApiStatusException(status)); } }); } };
  60. MessagePublishOperator @Override public Subscriber<? super Message> call(Subscriber<? super PublishResult> child)

    { final ApiClientWrapper apiClient = new ApiClientWrapper(this.context); return new Subscriber<Message>(child) { @Override public void onNext(Message message) { Nearby.Messages.publish(apiClient, message).setResultCallback(status -> { if (status.isSuccess()) { child.onNext(new PublishResult(message)); } else { child.onError(new ApiStatusException(status)); } }); } @Override public void onCompleted() { child.onCompleted(); } @Override public void onError(Throwable e) { child.onError(e); } }; }
  61. MessagePublishOperator @Override public Subscriber<? super Message> call(Subscriber<? super PublishResult> child)

    { final ApiClientWrapper apiClient = new ApiClientWrapper(this.context); return new Subscriber<Message>(child) { @Override public void onNext(Message message) { Nearby.Messages.publish(apiClient, message).setResultCallback(status -> { if (status.isSuccess()) { child.onNext(new PublishResult(message)); } else { child.onError(new ApiStatusException(status)); } }); } @Override public void onCompleted() { child.onCompleted(); } @Override public void onError(Throwable e) { child.onError(e); } }; }
  62. MessagePublishOperator private void init() { add(new Subscription() { @Override public

    void unsubscribe() { apiClient.unpublish(); apiClient.disconnect(); } @Override public boolean isUnsubscribed() { return apiClient.isConnected(); } }); }
  63. MessagePublishOperator private void init() { add(new Subscription() { @Override public

    void unsubscribe() { apiClient.unpublish(); apiClient.disconnect(); } @Override public boolean isUnsubscribed() { return apiClient.isConnected(); } }); }
  64. Takeaways • Call the child subscriber’s onNext()/onError()/onCompleted appropriately • Make

    sure any resources and the newly created parent subscriber are released when the child subscriber is unsubscribed • You can use new Subscriber(Subscriber) to make sure the subscription and the back-pressure chains ✓ Note, in that case, all the subscriptions are shared with a child subscriber, which may cause an unexpected behaviour in some cases (See http://akarnokd.blogspot.jp/2015/05/pitfalls- of-operator-implementations.html) • Take care of multiple subscriptions
  65. Summary • Subscriber is the core of RxJava • Custom

    Observable is pretty easy to create • Custom Operator is a bit more hard - Take care of subscriptions and back pressure • Read “RxJava Advanced” - Pitfalls of operator implementations (part 1)
 http://akarnokd.blogspot.jp/2015/05/pitfalls-of-operator- implementations.html