-
Notifications
You must be signed in to change notification settings - Fork 656
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
Prepare for query re-fetching #491
Prepare for query re-fetching #491
Conversation
Introduce separate interfaces for query and mutation calls. Generate OperationName type for operations to be used as identifier for refetching.
*/ | ||
@Nonnull ApolloMutationCall<T> refetchQueries(@Nonnull OperationName... operationNames); | ||
|
||
@Nonnull @Override ApolloMutationCall<T> cacheControl(@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.
@BenSchwab do you think cacheControl
makes sense for mutation as it defines fetch strategies and makes sense for queries only?
Though cacheHeaders
makes sense cos it defines if response for this mutation should be cached or not.
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.
how does caching a mutation work? Like what happens if you try same mutation again will you then get cached result instead of it hitting server?
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 shouldn't not hit the when we run mutations, but we should store the results to cache. So it's should WRITE ONLY. That's why I guess we need to remove cacheControl
and just have cacheHeaders
as they tells to save to cache or not
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.
Yes, cacheControl does not make sense for mutations. It will always be NETWORK_ONLY. If we write to the cache can be controlled by cacheHeaders
. It's generally important to write mutations results to the cache, as this is what triggers updates to apolloWatchers
.
@@ -62,7 +60,7 @@ public void testQueryWatcherUpdated_SameQuery_DifferentResults() throws IOExcept | |||
EpisodeHeroName query = EpisodeHeroName.builder().episode(Episode.EMPIRE).build(); | |||
server.enqueue(mockResponse("EpisodeHeroNameResponseWithId.json")); | |||
|
|||
ApolloWatcher<EpisodeHeroName.Data> watcher = apolloClient.newCall(query).watcher(); | |||
ApolloQueryWatcher<EpisodeHeroName.Data> watcher = apolloClient.query(query).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.
can you watch anything other than a query?
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.
Not now, we watch only Queries
private <D extends Operation.Data, T, V extends Operation.Variables> RealApolloCall<T> newCall( | ||
@Nonnull Operation<D, T, V> operation) { | ||
ResponseFieldMapper responseFieldMapper = responseFieldMapper(operation); | ||
return new RealApolloCall<T>(operation, serverUrl, httpCallFactory, httpCache, defaultHttpCachePolicy, moshi, |
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.
this constructor is wild! is there a way we can encapsulate somehow. Of the top of my head I see 2 options: builder or organization of params into a few objects.
ResponseFieldMapper responseFieldMapper; | ||
synchronized (responseFieldMapperPool) { | ||
responseFieldMapper = responseFieldMapperPool.get(operation.getClass()); | ||
if (responseFieldMapper == null) { |
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.
Sorry if this is out of scope for this pr but is there a way way can do a null object pattern or some other way to show absence without using null
. also missing nullability annotations
* | ||
* @param query the operation which needs to be performed | ||
* @return prepared {@link ApolloQueryCall} call to be executed at some point in the future | ||
*/ |
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 change the docs to:
/**
* Creates and prepares a new {@link ApolloMutationCall} call.
*
* @param mutation the mutation which needs to be performed
* @return prepared {@link ApolloMutationCall} call to be executed at some point in the future
*/
@@ -34,14 +34,14 @@ private Rx2Apollo() { | |||
} | |||
|
|||
/** | |||
* Converts an {@link ApolloWatcher} to an asynchronous Observable. | |||
* Converts an {@link ApolloQueryWatcher} to an asynchronous Observable. | |||
* | |||
* @param watcher the ApolloWatcher to convert. |
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 change it to:
@param watcher the ApolloQueryWatcher to convert.
@@ -35,27 +35,27 @@ private RxApollo() { | |||
} | |||
|
|||
/** | |||
* Converts an {@link ApolloWatcher} into an Observable. Honors the back pressure from downstream with the back | |||
* Converts an {@link ApolloQueryWatcher} into an Observable. Honors the back pressure from downstream with the back | |||
* pressure strategy {@link rx.Emitter.BackpressureMode#LATEST}. | |||
* | |||
* @param watcher the ApolloWatcher to convert |
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.
@param watcher the ApolloQueryWatcher to convert.
return from(watcher, Emitter.BackpressureMode.LATEST); | ||
} | ||
|
||
/** | ||
* Converts an {@link ApolloWatcher} into an Observable. | ||
* Converts an {@link ApolloQueryWatcher} into an Observable. | ||
* | ||
* @param watcher the ApolloWatcher to convert |
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.
@param watcher the ApolloQueryWatcher to convert.
@BenSchwab @digitalbuddha @marwanad any comments? |
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 OperationName sufficiently unique?
I forget if we allow two querries to have the same name so long as they are in different packages?
@BenSchwab just checked we don;t allow to have the same names for queries even in separate packages:
But it even if we allow to have the same names, for refetch we are going to use public final class TestQuery implements Query<TestQuery.Data, Optional<TestQuery.Data>, Operation.Variables> {
...
private static final OperationName OPERATION_NAME = new OperationName() {
@Override
public String name() {
return "TestQuery";
}
};
@Override
public OperationName name() {
return OPERATION_NAME;
} So it will be unique for every query |
public interface ApolloMutationCall<T> extends ApolloCall<T> { | ||
|
||
/** | ||
* <p>Sets a list of GraphQL query names to be re-fetched once this mutation completed.</p> |
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 this comment correct? Why do you need to obtain a ApolloQueryWatcher first?
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 will use ApolloQueryWatcher
to be notified when this mutation operation completed. So in order to listen for refetch user should have watcher first 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.
Well, we might want to refetch a query you aren't watching. Like if you have a post feed page, and a child page that creates a post, we might want to refetch the post feed query to update the normalized cache, even if we aren't watching
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.
Then if you happen to be watching the post page, and something did change, the watcher will be triggered when the refetched query is written to the normalized 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.
I was imagining you would just pass the actual queries to be refetched here instead of an identifier.
Also, what would happen if I was listening to the same query that was executed twice with different variables. Seems like the tags would collide.
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.
Yeah, makes sense. Tags can easily collide. So we just provide list of Query
objects to be refetched, so the only open question how to handle errors, just ignore? Mutation finished with success, but one of the refetched query failed.
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.
At the same time even if more than one watcher with the same name registered we will trigger them both
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.
Yeah, I think we would just have to ignore errors. Log them to our ApolloLogger of course.
Good point that we could just refetch all watchers with same tag. It's possible that we would be fetching more than the user actually wants, but if we batch the refetch, it doesn't seem like a big deal.
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.
Changes to the interfaces LGTM -- in terms of the refetching API, I would still lean towards being able to register Query objects vs tags just for the fact that it's unclear to me how watcher's will be used in practice, and being more generic seems like a plus. But if you want to land this, we can revisit when we implement the refetching behavior.
Introduce separate interfaces for query and mutation calls.
Generate OperationName type for operations to be used as identifier for refetching.
Part of #452