bruce_ricard
bruce_ricard

Reputation: 771

How does Observable single work?

when I call .single() on an observable, its type is "Observable<`?>" and if I pass it to an other method I don't believe it has any mean of knowing that it has been singled. I don't think I understand fully how this works. Any idea?

Upvotes: 4

Views: 7659

Answers (1)

lopar
lopar

Reputation: 2442

If the type isn't coming through you may need to either figure out why the signature isn't being carried through from the Observable calling it, or add it manually. So, for an Observable of type Foo:

myFooObservable.<Foo>single()

What single does is very similar to limit(1), except that it will also throw an error if the upstream Observable does not output exactly 1 item when it completes (including 0 items).

In general when designing an Observable chain, consumers of an Observable should be designed as if they can handle zero or more values. It's true that another method doesn't know how many things will be emitted from the Observable. If you want a subscriber to require only a single element to be outputted, it should add .single() to the Observable it was handed before subscribing.

For example, let's say you're given a getFoos method:

Observable<Foo> getFoos();

For some reason (maybe it's a library, or codegen, or aliens) you have no access to the implementation of this, so you don't know how many things (if any) it will emit. If you want to enforce one and only one thing, you can do:

getFoos().single().subscribe(
    (foo) -> /* Do something with foo */,
    (error) -> /* Oh noes there was an error. Maybe it completed with no output? */);

This will limit it to one and only one thing, and throw an error if it completes without outputting a Foo, or if it tries to output a second Foo.

If the getFoos method had single on the Observable it handed you, it should be the responsibility of the author of getFoos to either:

  1. Design and test it well enough that it will always emit only a single item. Probably the nicest approach for a consumer, API-wise, but this isn't always possible if external factors are involved, such as in #2...
  2. CLEARLY document that it may not always emit a single item, and will throw you an error if it does not, so you can handle that error. Not my favorite approach, but on some things (say, a network response) it makes sense to declare that you may emit an error if not exactly one item is emitted. The Retrofit library, for example, may throw RetrofitError exceptions if there are issues in any part of the network flow (I'm actually not positive this is clearly documented but given I figured it out pretty early on in using the app, I think it's easy enough to find). They're pretty good about making sure that things specific to the flow of the network request/response and parsing are wrapped in RetrofitErrors so if some serious runtime error happens that is not part of that flow, it will fall through and crash (as it usually should in that instance, as those are probably programming errors or serious, unrecoverable system errors.)
  3. Handle the error itself downstream of the single operator, possible by logging then emitting nothing if it ends up without a value. This is somewhere in the middle as far as my API tastes are concerned. Although it handles the error itself, there are legitimate cases for a consumer to want to handle and recover from errors, and this removes that ability. For example, one of my Android apps uses retry or retryWhen to figure out if a request should be retried, and I use various error transformers to apply those to set up different error handling behavior. Some requests I like to just log the error and move on. Others I like to do exponential backoff retries... Others I may want to do 1 or 2 immediate retries and then give up. Still others I might want to show a 'retry' UX button directly to the user.

If they don't meet at least 1 of those 3, I'd consider the API rather poorly designed, as the behavior is often going to be a surprise for consumers.

Some of this is a little abstract since I don't know your actual use case, so I'll be happy to clarify individual aspects of it if you have followup questions.

Upvotes: 4

Related Questions