apollographql / apollo-kotlin

:rocket:  A strongly-typed, caching GraphQL client for the JVM, Android, and Kotlin multiplatform.
https://www.apollographql.com/docs/kotlin
MIT License
3.71k stars 651 forks source link

Pagination helpers #3593

Open martinbonnin opened 2 years ago

martinbonnin commented 2 years ago

Using the normalized cache with lists and especially pages is cumbersome as different pages get stored in different records:

query GetRepositories($first: Int, $offset: Int) {
  repositories(first: $first, offset: $offset) {
    id
    owner
    name
  }
}

This will store the following root Record:

{
  "repositories(first: 100, offset: 0)": [
     { "owner": .. },
     { "owner": .. },
  ], 
  "repositories(first: 100, offset: 100)": [
     { "owner": .. },
     { "owner": .. },
  ], 
}

Because the data is spread between two Record fields, there's no easy way for a watcher to retrieve the full list.

API is yet to be defined.

Part of #2331

sonatard commented 2 years ago

And we need relayStylePagination helper function. https://www.apollographql.com/docs/react/pagination/cursor-based/#relay-style-cursor-pagination

pauldavies83 commented 2 years ago

We have a paginated query that returns us a list of our app items, like so…

query ItemsQuery($itemArgs: ItemArgs!) {
    items(args: $itemArgs) {
        edges {
            id
            node
        }
        pageInfo
    }
}

type ItemsArgs {
  first: Float
  after: String
  last: Float
  before: String
  filterBy: ItemsFilterByArgs
}

where the pageInfo contains the pagination info, and ItemsArgs allows us to request a “bucket” of data (usually a month of time’s worth).

The use case is for us to regularly sync our items from the server to the normalised cache, and our UI will watch the cache. It seems the only way for us to do this is to iterate over the pages in the query, and manually writeOperation the cache to populate it with all the edges (and key it against the original query with no pagination parameter).

It would be great if there was internal workings in the apolloClient to help us manage this.

pauldavies83 commented 2 years ago

Hey @martinbonnin 👋

Did this get prioritised internally? Just wondering if this is something we can expect in the short/medium term?

Thanks! 🙏

martinbonnin commented 2 years ago

Yes, it's one of the next big thing on the todo list after https://github.com/apollographql/apollo-kotlin/issues/3566. Pretty hard to commit to a date at this point but the coming months are the current goal.

martinbonnin commented 4 months ago

Hi 👋

We started investigating these APIs. You can read more about it in the design document.

The tldr; is:

  1. Import the incubating artifact:
// build.gradle.kts

dependencies {
  implementation("com.apollographql.apollo3:apollo-normalized-cache-incubating")
}
  1. extend your schema:
extend type Query @typePolicy(connectionFields: "usersConnection")
  1. configure your ApolloStore:
val apolloStore = ApolloStore(
  normalizedCacheFactory = cacheFactory,
  cacheKeyGenerator = TypePolicyCacheKeyGenerator,
  metadataGenerator = ConnectionMetadataGenerator(Pagination.connectionTypes),
  apolloResolver = FieldPolicyApolloResolver,
  recordMerger = ConnectionRecordMerger
)

Updating the cache now merges new items with the existing ones

[!WARNING] The persisted database format is different so you can't use it together with the non-incubating DB

  1. reading the cache now returns the full non-paginated list.

It's still the early days and too early to use in production but any feedback is warmly welcome.

sonatard commented 4 months ago

@martinbonnin Do you have plans to support the following format?

type UserConnection {
  pageInfo: PageInfo!
  nodes: [User!]!
}

Shopify and GitHub APIs have nodes in addition to edges. In Modern Relay, Cursors are no longer used, and instead startCursor and endCursor in PageInfo are used. Currently, Edge are an unnecessary layer.

スクリーンショット 2024-03-18 22 08 45 スクリーンショット 2024-03-18 22 09 14

https://shopify.dev/docs/api/admin-graphql/2024-01/queries/customers#returns

BoD commented 4 months ago

Hey @sonatard, this is great feedback! I've created issue #5735 to follow-up on this. Short answer is the current system is flexible enough to manually configure it to work with queries selecting nodes instead of edges but it would be better if it just worked automatically with the dedicated support we have for Relay style - I think we can do it without too much hurdles.