Reputation: 771
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
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:
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 RetrofitError
s 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.)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