-
-
Notifications
You must be signed in to change notification settings - Fork 628
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
Implement fetchMore API for pagination #144
Comments
Hi, we are currently evaluating this plugin and I also asked myself how to implement I could also open a pull request into the import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
typedef void FetchMoreCallback(
Map<String, dynamic> variables,
MergeResults mergeResults,
);
typedef Map<String, dynamic> MergeResults(
dynamic prev,
dynamic moreResults,
);
typedef Widget FetchMoreBuilder(
QueryResult result,
FetchMoreCallback fetchMore,
);
class QueryFetchMore extends StatefulWidget {
final QueryOptions options;
final FetchMoreBuilder builder;
const QueryFetchMore({Key key, this.options, this.builder}) : super(key: key);
@override
QueryFetchMoreState createState() {
return new QueryFetchMoreState();
}
}
class QueryFetchMoreState extends State<QueryFetchMore> {
bool _init = false;
GraphQLClient _client;
QueryResult _currentResult = QueryResult(loading: true);
@override
void didChangeDependencies() {
super.didChangeDependencies();
_client = GraphQLProvider.of(context).value;
_initialQuery();
}
void _initialQuery() async {
if (_init == false) {
_init = true;
final QueryResult result = await _client.query(widget.options);
setState(() {
_currentResult = result;
});
}
}
void _fetchMore(
Map<String, dynamic> variables,
MergeResults mergeResults,
) async {
setState(() {
_currentResult = QueryResult(data: _currentResult.data, loading: true);
});
final QueryOptions nextOptions = QueryOptions(
document: widget.options.document,
variables: variables,
fetchPolicy: widget.options.fetchPolicy,
errorPolicy: widget.options.errorPolicy,
pollInterval: widget.options.pollInterval,
context: widget.options.context,
);
final QueryResult result = await _client.query(nextOptions);
if (result.errors != null) {
setState(() {
_currentResult =
QueryResult(data: _currentResult.data, errors: result.errors);
});
return;
}
final QueryResult mergedResult = QueryResult(
data: mergeResults(_currentResult.data, result.data),
);
setState(() {
_currentResult = mergedResult;
});
}
@override
Widget build(BuildContext context) {
return widget.builder(_currentResult, _fetchMore);
}
} |
This is a very important feature to provide out of the box. To me it should be integrated in the same Query widget. @wwwdata have you ended up submitting your PR ? |
@olup not yet. Also noticed that I have to use the watcher to not break the "get stuff from cache first and then network" in my custom widget. So I have a slightly better version now. How would an ideal API look like to query the next page? Should we add this as parameter for the @HofmannZ what do you think would be a good API? |
@wwwdata good to hear ! At first, I thought mimicking the React implementation by having a method on Also I was wondering about exposing the last variables given to the request, so that we can easily implement infinite scroll, for example, just adding the offset or the page on the last query variable, without even tracking it on the widget calling the query. There is probably other use cases too for that. The last question for me would be if there should be a cache mechanism that could give back the all added query with latest variables - use case would be an infinite loading list that gets disposed at some point and would be called upon again later. It should show the all list. Right now, if i get it, cache works by keying each request with its variable. @HofmannZ for thoughts on those questions. |
@HofmannZ I meant exposing it through the builder, rather than mimicking Apollo and having it inside the result. So I completely agree with you :
Maybe other new tools could be exposed as such, so we could imagine :
|
@micimize Was this ever resolved? |
@mainawycliffe looking at apollo's
So essentially, future polling will just poll for the merged variables, which should describe the merged query according to the user |
With that in mind, i can make it work now. |
Please has this feature been added? |
@Wisdom0063 been caught at work lately, but i will find sometime and wrap up this, this week. |
Any progress on this issue? |
I have made some progress, will be available for testing either today or tomorrow. |
I have a first iteration of the fetch more feature here, please try it and see if it solves your use case, and provide feedback here. |
I have an idea. It's kind of like @wwwdata 's idea, but instead of keeping more data in the state we'd have a component called QueryFetchMore that gives a list of QueryResults instead of a QueryResult. Altough it is not apollo-like it doesn't prevent another API to be present in the future, but right now it seems that it would be a lot of work to get the cache and other features to work with a stateful solution as was proposed. |
@lucasavila00 The reason i didn't take that approach is because we are trying to closely match this library APIs to those of react-apollo. You can see how react-apollo does it the docs here. Here is an example from react-apollo: const FEED_QUERY = gql`
query Feed($type: FeedType!, $offset: Int, $limit: Int) {
currentUser {
login
}
feed(type: $type, offset: $offset, limit: $limit) {
id
# ...
}
}
`;
const FeedData = ({ match }) => (
<Query
query={FEED_QUERY}
variables={{
type: match.params.type.toUpperCase() || "TOP",
offset: 0,
limit: 10
}}
fetchPolicy="cache-and-network"
>
{({ data, fetchMore }) => (
<Feed
entries={data.feed || []}
onLoadMore={() =>
fetchMore({
variables: {
offset: data.feed.length
},
updateQuery: (prev, { fetchMoreResult }) => {
if (!fetchMoreResult) return prev;
return Object.assign({}, prev, {
feed: [...prev.feed, ...fetchMoreResult.feed]
});
}
})
}
/>
)}
</Query>
); As you can see the fetchMore method is exposed by the builder, which you can trigger anytime as i have done in the example i provided. And since we have to pass the fetchMore back to the builder, we have a breaking change. The problem with multiple components, is to what end because the API will continue to grow to support more features, which until dart supports union type definitions, they will be breaking changes, we have a similar issue with refetch. We can solve this by having a snapshot class for now that will contain query results, refetch and fetchmore call back, but it will still be a breaking change. |
I know apollo and I really like their api. I plan to submit the API I proposed on #176 for direct cache access and it wouldn't work with the API you proposed. I believe that my implementation isn't a breaking change because it's only a new class, it doesn't change any other file, but let me know if I'm wrong (I have never been a mantainer of something myself) and as a bonus it supports the current refetch implementation and the proposed direct cache access one. And if, in the future, when all the types are being cached and normalized my implementation wouldn't stop the api surface you're proposing to be implemented. |
I don't mind you supporting your own idea, but IMO i don't think we should branch from apollo implementation unless we have to. I don't think this feature warrants a new On top of that, i see you are now allowing the user to determine how the results are combined, or am i missing something. |
The user gets back a List of QueryResults and do with it as they please. |
That's the purpose of the FetchMoreOptions opts = FetchMoreOptions(
variables: {'cursor': pageInfo['endCursor']},
updateQuery: (prev, res) { // callback for how the data will be merged, the developer determines how to merge the database based on expected response.
final List<dynamic> repos = [
// in this just combine the list of repos from previous and current
...prev['search']['nodes'] as List<dynamic>,
...res['search']['nodes'] as List<dynamic>
];
// set it to the nodes of one and return the new response
res['search']['nodes'] = repos;
return res;
},
); About injecting type name, I will make the changes some changes by tomorrow and see if I can get it to work. Am thinking of adding query ids to the fetch more requests just like Apollo does, I think that will solve the problem. The other thing I noted with Apollo is that when a user submits a new documents, options of the previous queries are all ignored, in the end using fetch more options only. |
This issue can now be closed, it is currently available on beta and will be released to stable soon. If you come across any new bugs, issues, please create a new issue. |
@mainawycliffe im implemented fetchmore as you doen here ..its wors and data updates on the array .problem is query widget rebuilds when i calls fetchmore .after updates we can see the item from first.can you give me the solution for this? |
I have got the same problem than @AkhilSajiCodzoc, when using @mainawycliffe way to fetchmore pages on infinite scroll, the request seems to execute 2 times or more, and don't seems to load only the page we want but query everything since first page, each time, for each pages. Is it normal ? Is fetchmore API actualy work "out of the box" in V4 beta ? Thank's for your work anyway and Merry Christmas ! |
Is your feature request related to a problem? Please describe.
Currently, there is not an elegant way to do pagination with a GraphQL query. Apollo supports this thru the fetchMore API that is part of the query result.
Describe the solution you'd like
In Apollo, the fetchMore function allows you to do a new GraphQL query and merge the result into the original result. You can override variables, and provide a callback function to update the previous query.
Here's a simple example in in JavaScript
Describe alternatives you've considered
Right now the best solution I can come up with is to implement this logic with a GraphQLConsumer that gives me access to the GraphQLClient. This isn't the worst, but it does require quite a bit of boilerplate code.
Additional context
https://www.apollographql.com/docs/react/features/pagination.html
The text was updated successfully, but these errors were encountered: