aws-amplify / amplify-android

The fastest and easiest way to use AWS from your Android app.
https://docs.amplify.aws/lib/q/platform/android/
Apache License 2.0
245 stars 115 forks source link

[API] Subscribe with arguments (real time filtering) #580

Open chiragmittal19 opened 4 years ago

chiragmittal19 commented 4 years ago

There should be a way to pass arguments to subscriptions so that only a subset is subscribed.

Model schema -

type MyData @model {
  id: ID!
  x: Int
  other_data: String
}

Subscription method onUpdateMyData which take an input parameter x -

type Subscription {
    onUpdateMyData(x: Int): MyData
        @aws_subscribe(mutations: ["updateMyData"])
}

Mutation method updateMyData -

type Mutation {
    updateMyData(input: UpdateMyDataInput!, condition: ModelMyDataConditionInput): MyData
}

Following the official documentation, I wrote the below code for subscription. Works as expected, i.e, gets triggered whenever the updateMyData mutation is called, irrespective of the value of x.

Amplify.API.subscribe(ModelSubscription.onUpdate(MyData::class.java),
    { Log.i("ApiQuickStart", "Subscription established") },
    fun(onUpdate: GraphQLResponse<MyData>) {
        if (onUpdate.hasData()) {
            Log.i("ApiQuickStart", "Update: " + onUpdate.data.toString())
        } else if (onUpdate.hasErrors()) {
            Log.e("ApiQuickStart", "Error: " + onUpdate.errors.joinToString { it.message })
        } else {
             Log.i("ApiQuickStart", "Null update")
        }
    },
    { onFailure -> Log.e("ApiQuickStart", "Subscription failed", onFailure) },
    { Log.i("ApiQuickStart", "Subscription completed") }
)

But the problem is, I only want to subscribe for a specific value of x, say 5. So, I tried by modifying the request -

ModelSubscription.onUpdate(MyData::class.java).apply {
    putVariable("x", 5)
}

This made no difference. I'm still receiving updates for other values of x.

This is how I call updateMyData mutation from AppSync console -

mutation {
  updateMyData( input: {
    id: 1,
    x: 5,
    other_data: "some updated info"
  } ) {
    id, x, other_data
  }
}

Subscription in AppSync console works like a charm, i.e, it gets triggered only for a subset where x = 5 -

subscription {
  onUpdateMyData(x: 5) {
    id, x, other_data
  }
}

Just to mention, DynamoDB is used as the data source.

jamesonwilliams commented 4 years ago

@chiragmittal19

We could add a filtering argument to ModelSubscription.onUpdate(...), like:

public static <M extends Model> GraphQLRequest<M> onUpdate(
        Class<M> modelType, QueryPredicate filter) {
    ...
}

Is this what you have in mind?

This would constrain the set of results returned over the network, by means of a GraphQL predicate.

Since we don't have that, for the time-being, you could do the filtering on the client. I recommend using the Rx Bindings for this.

RxAmplify.API.subscribe(onUpdate(MyData::class.java))
    .filter { post -> !post.hasErrors() && post.hasData() }
    .map { post -> post.data }
    .filter { data -> data.x == "5" }
    .subscribe(
        { Log.i("Demo", "Found a match: $it") },
        { Log.i("Demo", "No matches.", it) }
    )
chiragmittal19 commented 4 years ago

A QueryPredicate sounds good. But in terms of performance and cost, will that be equivalent to passing an argument to the subscription method?

I guess it would be better to have an option to pass the complete string, maybe like this -

String subscriptionString = 
  ```subscription {
       onUpdateMyData(x: 5) {
         id, x, other_data
       }
     }```

Amplify.API.subscribe(subscriptionString,
    { Log.i("ApiQuickStart", "Subscription established") },
    fun(onUpdate: GraphQLResponse<MyData>) {
        if (onUpdate.hasData()) {
            Log.i("ApiQuickStart", "Update: " + onUpdate.data.toString())
        } else if (onUpdate.hasErrors()) {
            Log.e("ApiQuickStart", "Error: " + onUpdate.errors.joinToString { it.message })
        } else {
             Log.i("ApiQuickStart", "Null update")
        }
    },
    { onFailure -> Log.e("ApiQuickStart", "Subscription failed", onFailure) },
    { Log.i("ApiQuickStart", "Subscription completed") }
)
jamesonwilliams commented 4 years ago

@chiragmittal19 Thanks for the feedback! We prioritize work based on the number of reactions an issue receives. I'll close this issue for now, but let's leave it as a feature request.

If you are reading this and would like to see this feature implemented, please add a 👍 here: https://github.com/aws-amplify/amplify-android/issues/580#issue-639443740

nin234 commented 4 years ago

Yes would like this feature. Very helpful to reduce network traffic

w-okada commented 4 years ago

I also want this feature. Maybe this feature makes more secure in addition to moderating network traffic.

SteveJamesDev commented 2 years ago

For anyone who finds themselves here... AppSync has a feature that MIGHT help you like it helped me

https://docs.aws.amazon.com/appsync/latest/devguide/extensions.html

$extensions.setSubscriptionFilter

It allows you to add additional filters on the server side

vibhorkhurana580 commented 5 months ago

@jamesonwilliams Any update on the feature Request?

jamesonwilliams commented 5 months ago

Hey, @vibhorkhurana580 - sorry, I haven't worked on this project in a number of years. Overall I'd encourage folks to migrate to Apollo Kotlin, which also supports AppSync's flavor of WebSockets if you're stuck on that backend for now.