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
247 stars 117 forks source link

Amplify Codegen without @model directive does not create Java/Kotlin code for Queries #2807

Closed olliestringfield-accedo closed 6 months ago

olliestringfield-accedo commented 6 months ago

Before opening, please confirm:

Language and Async Model

Kotlin - Coroutines

Amplify Categories

Authentication, GraphQL API

Gradle script dependencies

```groovy // Put output below this line implementation("com.amplifyframework:core-kotlin:2.16.0") implementation("com.amplifyframework:aws-auth-cognito:2.16.0") implementation("com.amplifyframework:aws-api:2.16.0") ```

Environment information

``` # Put output below this line ------------------------------------------------------------ Gradle 8.4 ------------------------------------------------------------ Build time: 2023-10-04 20:52:13 UTC Revision: e9251e572c9bd1d01e503a0dfdf43aedaeecdc3f Kotlin: 1.9.10 Groovy: 3.0.17 Ant: Apache Ant(TM) version 1.10.13 compiled on January 4 2023 JVM: 17.0.9 (JetBrains s.r.o. 17.0.9+0-17.0.9b1087.7-11185874) OS: Mac OS X 14.4.1 aarch64 ```

Please include any relevant guides or documentation you're referencing

https://docs.amplify.aws/gen1/android/build-a-backend/graphqlapi/client-code-generation/

Describe the bug

We are implementing the Android Amplify SDK into a Kotlin Android app with no Amplify instance. Instead we are using a Cognito User Pool and AppSync. When putting the schema.graphql from AppSync into the root folder and running the codegen command:

amplify codegen add
>>> Choose the type of app that you're building **android**
>>> Enter the file name pattern of graphql queries, mutations and subscriptions **app/src/main/graphql/**/*.graphql**
>>> Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions **Yes**
>>> Enter maximum statement depth [increase from default if your schema is deeply nested] 2
✔ Generated GraphQL operations successfully and saved at app/src/main/graphql/com/amazonaws/amplify/generated/graphql

Only .graphql files are generated i.e app/src/main/graphql/com/amazonaws/amplify/generated/graphql/queries.graphql There is no generated Java or Kotlin code to use with Amplify SDK and use our queries defined in the schema file. Note the schema is not using the @model directive since we are not using Dynamo DB

Reproduction steps (if applicable)

No response

Code Snippet

// Put your code below this line.

Log output

``` // Put your logs below this line ```

amplifyconfiguration.json

No response

GraphQL Schema

```graphql // Put your schema below this line schema { query: Query mutation: Mutation } type ModelProductConnection @aws_cognito_user_pools { items: [Product]! nextToken: String } type Mutation { createProduct(condition: ModelProductConditionInput, input: CreateProductInput!): Product @aws_cognito_user_pools deleteProduct(condition: ModelProductConditionInput, input: DeleteProductInput!): Product @aws_cognito_user_pools updateProduct(condition: ModelProductConditionInput, input: UpdateProductInput!): Product @aws_cognito_user_pools } type Product @aws_cognito_user_pools { createdAt: AWSDateTime! description: String id: ID! image: String name: String! owner: String price: Float updatedAt: AWSDateTime! userId: String userName: String } type Query { getProduct(id: ID!): Product @aws_cognito_user_pools listProducts(filter: ModelProductFilterInput, limit: Int, nextToken: String): ModelProductConnection @aws_cognito_user_pools } enum ModelAttributeTypes { _null binary binarySet bool list map number numberSet string stringSet } enum ModelSortDirection { ASC DESC } input CreateProductInput { description: String id: ID image: String name: String! price: Float userId: String userName: String } input DeleteProductInput { id: ID! } input ModelBooleanInput { attributeExists: Boolean attributeType: ModelAttributeTypes eq: Boolean ne: Boolean } input ModelFloatInput { attributeExists: Boolean attributeType: ModelAttributeTypes between: [Float] eq: Float ge: Float gt: Float le: Float lt: Float ne: Float } input ModelIDInput { attributeExists: Boolean attributeType: ModelAttributeTypes beginsWith: ID between: [ID] contains: ID eq: ID ge: ID gt: ID le: ID lt: ID ne: ID notContains: ID size: ModelSizeInput } input ModelIntInput { attributeExists: Boolean attributeType: ModelAttributeTypes between: [Int] eq: Int ge: Int gt: Int le: Int lt: Int ne: Int } input ModelProductConditionInput { and: [ModelProductConditionInput] description: ModelStringInput image: ModelStringInput name: ModelStringInput not: ModelProductConditionInput or: [ModelProductConditionInput] price: ModelFloatInput userId: ModelStringInput userName: ModelStringInput } input ModelProductFilterInput { and: [ModelProductFilterInput] description: ModelStringInput id: ModelIDInput image: ModelStringInput name: ModelStringInput not: ModelProductFilterInput or: [ModelProductFilterInput] price: ModelFloatInput userId: ModelStringInput userName: ModelStringInput } input ModelSizeInput { between: [Int] eq: Int ge: Int gt: Int le: Int lt: Int ne: Int } input ModelStringInput { attributeExists: Boolean attributeType: ModelAttributeTypes beginsWith: String between: [String] contains: String eq: String ge: String gt: String le: String lt: String ne: String notContains: String size: ModelSizeInput } input UpdateProductInput { description: String id: ID! image: String name: String price: Float userId: String userName: String } ```

Additional information and screenshots

Output queries.graphql

# this is an auto generated file. This will be overwritten

query GetProduct($id: ID!) {
  getProduct(id: $id) {
    createdAt
    description
    id
    image
    name
    owner
    price
    updatedAt
    userId
    userName
    __typename
  }
}

query ListProducts(
  $filter: ModelProductFilterInput
  $limit: Int
  $nextToken: String
) {
  listProducts(filter: $filter, limit: $limit, nextToken: $nextToken) {
    items {
      createdAt
      description
      id
      image
      name
      owner
      price
      updatedAt
      userId
      userName
      __typename
    }
    nextToken
    __typename
  }
}
tylerjroach commented 6 months ago

@olliestringfield-accedo This is expected. The Amplify Android client library is built to use Amplify models and it does not use the .graphql files generated. To use non amplify models, the Android AppSync SDK is still available (https://github.com/awslabs/aws-mobile-appsync-sdk-android)

olliestringfield-accedo commented 6 months ago

@tylerjroach Thanks for the recommendation. This is odd because the iOS Amplify SDK allows querying for non-Amplify models and the codegen generates queries to use with the SDK without the @model directive. Is this a known feature discrepancy between Android and iOS that will not be aligned?

tylerjroach commented 6 months ago

@olliestringfield-accedo Can you show me a code example for Swift of what you are doing? I want to make sure I'm not misunderstanding anything.

One difference between Swift and Android is that the AWS AppSync SDK for iOS has entered maintenance mode, and the Amplify API category was updated to support a few of the AppSync SDK use cases.

olliestringfield-accedo commented 6 months ago

So given the attached schema.graphql file. When I run amplify add codegen, the generated queries are output to the API.swift file.

We can see the generated GetProductQuery class

public final class GetProductQuery: GraphQLQuery {
  public static let operationString =
    "query GetProduct($id: ID!) {\n  getProduct(id: $id) {\n    __typename\n    createdAt\n    description\n    id\n    image\n    name\n    owner\n    price\n    updatedAt\n    userId\n    userName\n  }\n}"

  public var id: GraphQLID

  public init(id: GraphQLID) {
    self.id = id
  }

  public var variables: GraphQLMap? {
    return ["id": id]
  }
  ...
}

Then we can use this to create a GraphQLRequest<R: Decodable> object to pass through to the Amplify API category as shown below

    func getProduct(id: String) async throws -> GetProductQuery.Data {
        do {
            let query = GetProductQuery(id: id)
            let operation = GetProductQuery.operationString
            let responseType = GetProductQuery.Data.self
            let request = GraphQLRequest(document: operation, variables: query.variables, responseType: responseType)
            let result = try await Amplify.API.query(request: request)
            switch result {
            case .success(let data):
                return data
            case .failure(let error):
                debugPrint(error.errorDescription)
                throw error
            }
        } catch let error {
            debugPrint("Failed to get product id=\(id)")
            throw error
        }
    }

I've added the full generated API.swift for reference API.swift.zip

tylerjroach commented 6 months ago

Hi @olliestringfield-accedo, the use case you are using on the Swift side is the one found in the upgrade guide to use the AppSync SDK API.swift file that was previously used with AppSync iOS SDK. When the SDK was placed into maintenance mode, support was added on Amplify Swift side to allow this file to be used.

Since the Android AppSync SDK is still supported, this migration path has not been enabled. The expectation is still to use the AppSync Android SDK.

olliestringfield-accedo commented 6 months ago

Thanks for the clarification @tylerjroach. I'll use the Android AppSync SDK for our use case

github-actions[bot] commented 6 months ago

This issue is now closed. Comments on closed issues are hard for our team to see. If you need more assistance, please open a new issue that references this one.