Apollo-android: Is it possible to watch the changes to the normalized cache records directly from ApolloStore?

Created on 3 Aug 2019  路  11Comments  路  Source: apollographql/apollo-android

Currently, we can read Graphql Fragment directly from ApolloStore like this:

apolloStore().read(
  UserFragment.Mapper(),
  CacheKey.from("myKey"),
  Operation.EMPTY_VARIABLES
)

Is it possible to watch the changes to the fragment, so that the caller get notified when there is a new update? Something similar to ApolloQueryWatcher.

Normalized cache Enhancement

Most helpful comment

That would be a really awesome feature actually!
Observability is really nice to have for certain use cases.

Looking at how Room is doing internally, they're invalidating any active LiveData when a change in a "watched" table happens (by table name, logic can be seen in androidx.room.InvalidationTracker).
If Apollo supported something similar (LiveData invalidation per Fragment, and even per Key), that would be really cool!

Wdyt?

All 11 comments

I guess you can try to register own RecordChangeSubscriber via com.apollographql.apollo.cache.normalized.ApolloStore#subscribe

I just have a look at ApolloStore#subscribe today.
ApolloStore#subscribe get notify on every change, but I want to get notified only on the change that affect my target Fragment.

Is it a good idea (or even possible) to have watch function in ApolloStore?
Then it is possible to watch the Fragment like this:

apolloStore().watch(
    UserFragment.Mapper(),
    CacheKey.from("myKey"),
    Operation.EMPTY_VARIABLES
).enqueueAndWatch(object: ApolloStoreOperation.Callback<UserFragment> {
    override fun onSuccess(result: UserFragment?) {
        // Get notified when UserFragment get updated
    }
    override fun onFailure(t: Throwable?) {

    }
})

If there is nothing against this idea, I would like to help on this feature.

That would be a really awesome feature actually!
Observability is really nice to have for certain use cases.

Looking at how Room is doing internally, they're invalidating any active LiveData when a change in a "watched" table happens (by table name, logic can be seen in androidx.room.InvalidationTracker).
If Apollo supported something similar (LiveData invalidation per Fragment, and even per Key), that would be really cool!

Wdyt?

Any news about this one? :)

In the world of android where we don't own the creation of objects such as activity, fragment and architecture_component::viewmodel it will be really helpful.

@teodorpenkov Improving the cache is pretty high in the ROADMAP. Real-life use case would be super helpful to make sure the new APIs will work for your environment. Could you ellaborate more on how you plan to use Store.watchFragment() and especially why query watchers would not work?

Let's say we have master -> detail relationship.
On the master page we paginate through some data. Tapping on an item open its details, from there we could perform some update operations. The changes are propagated to the master screen via query watchers, but not propagated to the details without some kind of imperative reloading.

On Android it's pretty painful to pass more complex objects between screens, google's shared view model suggested approach is also weird because the master -> detail relationship might be very complex (given that details perform updates etc.).

So what I'm trying to do is to pass an id to the details screen and observe updates in there and reactively update the ui based on the latest state in the cache.

Thanks for the details!

On the master page we paginate through some data. Tapping on an item open its details, from there we could perform some update operations.

I'm guessing you have one big query for the master page that also loads all the details?

query GetPage($page: Int!) {
   items(page: $page) {
    # these are the details 
    id
    title
    thumbnail
    upvotes
  }
}

The changes are propagated to the master screen via query watchers, but not propagated to the details without some kind of imperative reloading.

You could use a query watcher for the details page as well:

query GetItem($id: String!) {
  item(id: $id) {
    id
    title
    thumbnail
    upvotes
  }
}

Then you could use a watcher there as well. incrementing the upvotes would trigger both the master and details watchers. Would that work?

Yes, that's an option, but opening details will make extra query to the server unless I manually update the cache for each node in the master query.

Yes, that's an option, but opening details will make extra query to the server unless I manually update the cache for each node in the master query.

Ideally, a good CacheKeyResolver would make it possible to load the details without a network roundtrip. There are some details there: https://www.apollographql.com/docs/android/essentials/normalized-cache/

This API is not easy to grasp so that's definitely an area where we want to make it easier but for this specific use case, I think that'd be the easiest solution.

Hi @teodorpenkov

I solved this problem by using the queryWatcher, thus I don't need this feature anymore.

For master/detail screen, I use CacheKeyResolver below.

val resolver = object : CacheKeyResolver() {
  override fun fromFieldRecordSet(field: ResponseField, recordSet: MutableMap<String, Any>): CacheKey { 
    return formatCacheKey((recordSet["id"] as? String))
  }

  override fun fromFieldArguments(field: ResponseField, variables: Operation.Variables): CacheKey {
    return formatCacheKey(field.resolveArgument("id", variables) as? String)
  }

  private fun formatCacheKey(id: String?): CacheKey {
    return if (id == null || id.isEmpty()) {
      CacheKey.NO_KEY 
    } else {
      CacheKey.from(id)
    }
  }
}

With this code, every record with the field id will be stored in ApolloStore with id as its key. Also, every query which contains field id will be looked up for a corresponding record in ApolloStore. So when you query item(id: 'someId'), it will return the record with 'someId' in ApolloStore.

By the way, I'm not sure if this solution will always working in future release. apollo-ios doesn't resolve cache key like this and I can't use this solution in iOS.

@martinbonnin Do you have any suggestion about this solution?

Was this page helpful?
0 / 5 - 0 ratings