-
Notifications
You must be signed in to change notification settings - Fork 660
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 Query Watching #312
Add Query Watching #312
Conversation
67cd4cb
to
902a8aa
Compare
} | ||
|
||
@Nonnull public WatcherSubscription enqueueAndWatch(@Nullable final ApolloCall.Callback<T> callback) { | ||
this.callback = callback; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we add safe guard if we already watching? I mean we don't support multiple subscriptions right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
} | ||
|
||
private void fetch() { | ||
activeCall.enqueue(callbackProxy(callback, activeCall)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be careful, active call can't be run multiple times, should it be activeCall.clone()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
enqueueAndWatch
should only be called once -- I can add some safeguards for that. But enqueueAndWatch
just calls the ApolloCall that this was transformed from, so that should never have been executed before.
|
||
@Nonnull public RealApolloWatcher<T> refetch() { | ||
activeCall.cancel(); //Todo: is this necessary / good? We don't want people to chain refetch().refetch() | ||
activeCall = activeCall.clone().cacheControl(refetchCacheControl); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this line should be moved to fetch
function, it will be easier to understand that we actually doing activeCall.clone() before enqueue
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So we actually aren't cloning before enqueueAndWatch
only from refetch
. Per other comments, it is only possible to transform a ApolloCall
to an ApolloWatcher
if it has not been executed. However, calls to refetch
will clone the ApolloCall
because in this case it has been used. I haven't convinced myself that refetch
as a lot of value -- but added it because it is part of the iOS watcher interface.
@@ -74,7 +76,7 @@ private RealApolloCall(Operation operation, HttpUrl serverUrl, Call.Factory http | |||
return executeNetworkRequest(); | |||
} | |||
|
|||
@Nonnull @Override public ApolloCall<T> enqueue(@Nullable final Callback<T> callback) { | |||
@Nonnull @Override public void enqueue(@Nullable final Callback<T> callback) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what the reason to remove return value?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And can we have like this: apollocall.enqueue(callback).watch()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to guarantee that the a Call has been executed when we watch. Otherwise we have no dependentKeys to monitor. So I don't like relying on have the user chain apollocall.enqueue().watch()
. Instead I have it has apolloCall.toWatcher().enqueueAndWatch()
.
Is there a value in having enqueue
return an ApolloCall? OkHttp has void return null. It just seems dangerous. Once you call enqueue
many functions on that ApolloCall
now explode, and returning it seems to imply that the object is still safe to use.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a value in having enqueue return an ApolloCall
no just was curious, thx makes sense!!
@@ -9,22 +15,35 @@ | |||
public final class RealCache implements Cache { | |||
private final CacheStore cacheStore; | |||
private final CacheKeyResolver cacheKeyResolver; | |||
private Map<RecordChangeSubscriber, Set<String>> subscribers; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we make RecordChangeSubscriber
as weak ref? So WeakHashMap?
Then it will be user responsibility to keep around subscribtion?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 Good Idea!
One more question do we have a vision how query watching will utilize RxJava? |
It's going to use rxjava fromEmitter/create api which can wrap any async call into rx. I have good idea how to get that piece in. |
Here was original idea for rx support. I don't see why it won't work for this too. https://gist.github.com/digitalbuddha/3c1dbccf059f0279e46e540b2c5fd89e |
902a8aa
to
a93c1f0
Compare
import javax.annotation.Nonnull; | ||
import javax.annotation.Nullable; | ||
|
||
final class RealApolloWatcher<T extends Operation.Data> implements ApolloWatcher<T> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to have cancel
or dispose
method, to cancel watching via RealApolloWatcher
as well not only via WatcherSubscription
return this; | ||
} | ||
|
||
@Nonnull public RealApolloWatcher<T> refetch() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess we can merge refetchCacheControl
with this method, so to have 2 API calls:
public RealApolloWatcher<T> refetch(@Nonnull CacheControl cacheControl)
public RealApolloWatcher<T> refetch()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overloading seems fair -- but we need to keep refetchCacheControl
because it plays a roll in enqueueAndWatch
because when dependenKeys change refetchCacheControl
is used to make the new request. So call.toWatcher().refetchCacheControl(CACHE_ONLY).enqueueAndWatch(..)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My intention was to make it as much immutable as possible, after we called enqueueAndWatch what if we change the cache control refetchCacheControl
? Should we add safe guard to prevent user from changing cache control after he called enqueueAndWatch
?
} | ||
|
||
@Nonnull @Override public ApolloCall<T> httpCacheControl(@Nonnull HttpCacheControl httpCacheControl) { | ||
@Nonnull @Override public RealApolloWatcher<T> toWatcher() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens if I call this multiple times for the same RealApolloCall
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With clone
, this would be acceptable. I don't see a real harm in allowing it, but it does go against the explicit single-use immutable style of the other methods.
} | ||
|
||
@Nonnull @Override public ApolloCall<T> httpCacheControl(@Nonnull HttpCacheControl httpCacheControl) { | ||
@Nonnull @Override public RealApolloWatcher<T> toWatcher() { | ||
return new RealApolloWatcher<>(this, cache); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't we call new RealApolloWatcher<>(clone(), cache)
?
@@ -12,33 +20,41 @@ | |||
private final CacheStore cacheStore; | |||
private final CacheKeyResolver cacheKeyResolver; | |||
private final ReadWriteLock lock; | |||
private Map<RecordChangeSubscriber, Set<String>> subscribers; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
final
this.cache = cache; | ||
} | ||
|
||
public void enqueueAndWatch(@Nullable final ApolloCall.Callback<T> callback) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: @OverRide
activeCall.enqueue(callbackProxy(this.callback, activeCall)); | ||
} | ||
|
||
@Nonnull public RealApolloWatcher<T> refetchCacheControl(@Nonnull CacheControl cacheControl) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: @OverRide
@Nonnull ApolloCall<T> enqueue(@Nullable Callback<T> callback); | ||
void enqueue(@Nullable Callback<T> callback); | ||
|
||
@Nonnull ApolloWatcher<T> toWatcher(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: maybe simple watcher()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Small comments, but lgtm. Great work
Thanks for the reviews! |
closes #304
This is a WIP query watching PR. There will be many more tests, naming will be reconsidered, TODOs fixed etc...
But wanted to get early thoughts on general design.
I played around with couple ways, but settled on have an the flow
ApolloCall.toWatcher()
->ApolloWatcher
The reason is that in order to watch a query you need to execute the query at least once to generate the
dependentKeys
. So a user would first need to configure the "root query" with whatever cache control they want, and then transform it into a watcher.Then they call
enqueueAndWatch
which performs the initial fetch. Upon successful fetch, the watcher then subscribes to change keys events on the cache.Importantly,
refetch
cachePolicy is separate than the initial fetch policy.One thing to take a look at: whenever a query is refetched, we actually unsubscribe from cache changed keys event, until the query is returned with new
dependentKeys
. This is because when there is an outgoing request, the current dependentKeys are "stale". When we move to more sophisticated threading model this will be important.