amplify-flutter icon indicating copy to clipboard operation
amplify-flutter copied to clipboard

GraphQLResponseError: Cannot return null for non-nullable field

Open amrogad opened this issue 4 months ago • 7 comments

Description

When we added a new field to 2 of our models, we started getting this error for other fields, even though we didn't touch them, for example, we added price_adjustments to Building and User models, then we started getting this error for AWSDateTime for the Client model, the client model wasn't touched, same goes for id or GenericStatus, we only added the price_adjustments field and it was nullable, so not sure what broke everything, here are some examples, there was 17 error messages in total for all the fields in Client, User and Building models

[GETX] [DBG] [flutter_amplifysdk/models/generic.dart:199] <String> In Update.updateData => errors: [GraphQLResponseError{
  "message": "Cannot return null for non-nullable type: 'ID' within parent 'Building' (/updateCart/building/id)",
  "path": [
    "updateCart",
    "building",
    "id"
  ]
}, GraphQLResponseError{
  "message": "Cannot return null for non-nullable type: 'String' within parent 'Building' (/updateCart/building/name)",
  "path": [
    "updateCart",
    "building",
    "name"
  ]
}, GraphQLResponseError{
  "message": "Cannot return null for non-nullable type: 'GenericStatus' within parent 'Building' (/updateCart/building/status)",
  "path": [
    "updateCart",
    "building",
    "status"
  ]
}, GraphQLResponseError{
  "message": "Cannot return null for non-nullable type: 'AWSDateTime' within parent 'Building' (/updateCart/building/createdAt)",
  "path": [
    "updateCart",
    "building",
    "createdAt"
  ]
}, GraphQLResponseError{
  "message": "Cannot return null for non-nullable type: 'AWSDateTime' within parent 'Building' (/updateCart/building/updatedAt)",
  "path": [
    "updateCart",
    "building",
    "updatedAt"
  ]
}, GraphQLResponseError{
  "message": "Cannot return null for non-nullable type: 'ID' within parent 'User' (/updateCart/user/id)",
  "path": [
    "updateCart",
    "user",
    "id"
  ]
}, GraphQLResponseError{
  "message": "Cannot return null for non-nullable type: 'String' within parent 'User' (/updateCart/user/email)",
  "path": [
    "updateCart",
    "user",
    "email"
  ]
}, GraphQLResponseError{
  "message": "Cannot return null for non-nullable type: 'GenericStatus' within parent 'User' (/updateCart/user/status)",
  "path": [
    "updateCart",
    "user",
    "status"
  ]
}, GraphQLResponseError{
  "message": "Cannot return null for non-nullable type: 'AWSDateTime' within parent 'User' (/updateCart/user/createdAt)",
  "path": [
    "updateCart",
    "user",
    "createdAt"
  ]
}, GraphQLResponseError{
  "message": "Cannot return null for non-nullable type: 'AWSDateTime' within parent 'User' (/updateCart/user/updatedAt)",
  "path": [
    "updateCart",
    "user",
    "updatedAt"
  ]
}, GraphQLResponseError{
  "message": "Cannot return null for non-nullable type: 'ID' within parent 'Client' (/updateCart/client/id)",
  "path": [
    "updateCart",
    "client",
    "id"
  ]
}, GraphQLResponseError{
  "message": "Cannot return null for non-nullable type: 'String' within parent 'Client' (/updateCart/client/firstname)",
  "path": [
    "updateCart",
    "client",
    "firstname"
  ]
}, GraphQLResponseError{
  "message": "Cannot return null for non-nullable type: 'String' within parent 'Client' (/updateCart/client/lastname)",
  "path": [
    "updateCart",
    "client",
    "lastname"
  ]
}, GraphQLResponseError{
  "message": "Cannot return null for non-nullable type: 'String' within parent 'Client' (/updateCart/client/email)",
  "path": [
    "updateCart",
    "client",
    "email"
  ]
}, GraphQLResponseError{
  "message": "Cannot return null for non-nullable type: 'String' within parent 'Client' (/updateCart/client/telephone)",
  "path": [
    "updateCart",
    "client",
    "telephone"
  ]
}, GraphQLResponseError{
  "message": "Cannot return null for non-nullable type: 'AWSDateTime' within parent 'Client' (/updateCart/client/createdAt)",
  "path": [
    "updateCart",
    "client",
    "createdAt"
  ]
}, GraphQLResponseError{
  "message": "Cannot return null for non-nullable type: 'AWSDateTime' within parent 'Client' (/updateCart/client/updatedAt)",
  "path": [
    "updateCart",
    "client",
    "updatedAt"
  ]
}]

We reverted back using cloud formation before adding the new price_adjustments fields, but the error was still there, what could've caused this error? How come it wasn't fixed by the revert? What would be the fix? Also is it possible to copy the same architecture from prod env and make a brand new dev env? We don't really care about the data we just need the fastest solution.

Categories

  • [ ] Analytics
  • [ ] API (REST)
  • [x] API (GraphQL)
  • [ ] Auth
  • [ ] Authenticator
  • [x] DataStore
  • [ ] Notifications (Push)
  • [ ] Storage

Steps to Reproduce

1- Add a new nullable field to the schema (e.g. price_adjustments). 2- Push the changes. 3- Make any GraphQL requests that involve the Building, User, or Client models (e.g. updateCart).

Screenshots

No response

Platforms

  • [x] iOS
  • [x] Android
  • [ ] Web
  • [ ] macOS
  • [ ] Windows
  • [ ] Linux

Flutter Version

3.27.1

Amplify Flutter Version

1.8.0

Deployment Method

Amplify CLI (Gen 1)

Schema

enum GenericStatus {
  ACTIVE
  INACTIVE
  DELETED
}

enum WindowStatus {
  not_yet_measured
  select_styles
  order_pending
  order_received
  order_in_progress
  order_in_transit
  wait_for_measurements
  not_yet_installed
  installed
  pairing_pending
  paired
  deleted
}

enum FamilyRole {
  OWNER
  KID
  USER
  ADMIN
}

enum AddressType {
  billing
  shipping
}

type ProductCategory @model @auth(rules: [{allow: private, operations: [read]}, {allow: private, provider: iam}]) {
  id: ID!
  name: String!
  verbose_name: String
  notes: String
  status: GenericStatus!
  data: String
  custom_fields: String
  components: [Component] @manyToMany(relationName: "ProductCategoryComponent")
  products: [Product] @manyToMany(relationName: "ProductCategoryProduct")
  genericproductID: ID @index(name: "byGenericProduct")
  genericproduct: GenericProduct @belongsTo(fields: ["genericproductID"])
}

type Component @model @auth(rules: [{allow: private, operations: [read]}, {allow: private, provider: iam}]) {
  id: ID!
  name: String!
  verbose_name: String
  notes: String
  status: GenericStatus!
  data: String
  custom_fields: String
  productcategories: [ProductCategory] @manyToMany(relationName: "ProductCategoryComponent")
}

type Product @model @auth(rules: [{allow: private, operations: [read]}, {allow: private, provider: iam}]) {
  id: ID!
  name: String!
  verbose_name: String
  description: String
  product_line: String
  external_id: String
  price: Float
  notes: String
  status: GenericStatus!
  data: String
  custom_fields: String
  manufacturerID: ID
  manufacturer: Manufacturer @hasOne(fields: ["manufacturerID"])
  componentID: ID
  component: Component @hasOne(fields: ["componentID"])
  categories: [ProductCategory] @manyToMany(relationName: "ProductCategoryProduct")
  genericproductID: ID @index(name: "byGenericProduct")
  genericproduct: GenericProduct @belongsTo(fields: ["genericproductID"])
  
  #For ticket#957
  #supplierID: ID  @index(name: "bySupplier", sortKeyFields: ["name"])
  #supplier: User @belongsTo(fields: ["supplierID"])
}

type Manufacturer @model @auth(rules: [{allow: private, operations: [read]}, {allow: private, provider: iam}]) {
  id: ID!
  name: String
  verbose_name: String
  notes: String
  status: GenericStatus!
  data: String
  custom_fields: String
}

type Partner @model @auth(rules: [{allow: private, operations: [read]}, {allow: private, provider: iam}]) {
  id: ID!
  name: String
  verbose_name: String
  address: String
  contact: String
  notes: String
  status: GenericStatus!
  data: String
  custom_fields: String
}


type GenericProduct @model @auth(rules: [{allow: private, operations: [read]}, {allow: private, provider: iam}]) {
  id: ID!
  name: String!
  verbose_name: String
  configuration: String
  notes: String
  status: GenericStatus!
  data: String
  custom_fields: String
  products: [Product] @hasMany(indexName: "byGenericProduct", fields: ["id"])
  categories: [ProductCategory] @hasMany(indexName: "byGenericProduct", fields: ["id"])
}

type Address @model @auth(rules: [{allow: owner, identityClaim: "sub", ownerField: "users"}, {allow: private, provider: iam}]) {
  id: ID!
  name: String!
  line1: String!
  line2: String
  city: String!
  state: String!
  country: String!
  postalCode: String!
  type: AddressType
  isBilling: Boolean
  isShipping: Boolean
  isSelected: Boolean
  status: GenericStatus!
  data: String
  users: [String!]
  families: [Family] @hasMany(indexName: "byAddressFamily", fields: ["id"])
}

type Window @model @auth(rules: [{allow: owner, identityClaim: "sub", ownerField: "users"}, {allow: private, provider: iam}]) {
  id: ID!
  name: String!
  status: WindowStatus!
  data: String
  users: [String!]
  roomID: ID!
  room: Room @hasOne(fields: ["roomID"])
  families: [Family] @hasMany(indexName: "byWindowFamily", fields: ["id"])
  orderID: String
  orderStatus: String #suborder status
  notes: String
}

type Room @model @auth(rules: [{allow: owner, identityClaim: "sub", ownerField: "users"}, {allow: private, provider: iam}]) {
  id: ID!
  name: String!
  status: GenericStatus!
  data: String
  buildingID: ID!
  building: Building @hasOne(fields: ["buildingID"])
  users: [String!]
  families: [Family] @hasMany(indexName: "byRoomFamily", fields: ["id"])
}

type Building @model @auth(rules: [{allow: owner, identityClaim: "sub", ownerField: "users"}, {allow: private, provider: iam}]) {
  id: ID!
  name: String!
  status: GenericStatus!
  data: String
  users: [String!]
  families: [Family] @hasMany(indexName: "byBuildingFamily", fields: ["id"])
  carts: [Cart] @hasMany(indexName: "byCartBuilding", fields: ["id"])
  notes: String
}

# @manyToMany creates a relationship table FamilyUsers
type Family @model @auth(rules: [{allow: owner, , identityClaim: "sub"}, {allow: private, provider: iam}]) {
  id: ID!
  name: String!
  status: GenericStatus!
  data: String
  #members: [User] @manyToMany(relationName: "FamilyUsers")
  members: [FamilyUser] @hasMany(indexName: "byFamilyUserFamily", fields: ["id"])
  windowID: ID @index(name: "byWindowFamily", sortKeyFields: ["name"])
  window: Window @belongsTo(fields: ["windowID"])
  roomID: ID @index(name: "byRoomFamily", sortKeyFields: ["name"])
  room: Room @belongsTo(fields: ["roomID"])
  buildingID: ID @index(name: "byBuildingFamily", sortKeyFields: ["name"])
  building: Building @belongsTo(fields: ["buildingID"])
  addressID: ID @index(name: "byAddressFamily", sortKeyFields: ["name"])
  address: Address @belongsTo(fields: ["addressID"])
}

type FamilyUser @model @auth(rules: [{ allow: owner, identityClaim: "sub"}, { allow: private, operations: [create,read,update] },{ allow: private, provider: iam }]) {
    id: ID!
    familyID: ID @index(name: "byFamilyUserFamily", sortKeyFields: ["memberID"])
    memberID: ID @index(name: "byFamilyUserUser", sortKeyFields: ["familyID"])
    family: Family @belongsTo(fields: ["familyID"])
    member: User @belongsTo(fields: ["memberID"])
    role: FamilyRole!
    status: GenericStatus!
    data: String
}

type User @model @auth(rules: [{allow: owner, identityClaim: "sub"}, {allow: private, provider: iam}]) {
  id: ID!
  email: String!
  status: GenericStatus!
  data: String
  #families: [Family] @manyToMany(relationName: "FamilyUsers")
  families: [FamilyUser] @hasMany(indexName: "byFamilyUserUser", fields: ["id"])
  carts: [Cart] @hasMany(indexName: "byCartUser", fields: ["id"])
  #For ticket#957
  #products: [Product] @hasMany(indexName: "bySupplier", fields: ["id"])
}
 
type Client @model @auth(rules: [{ allow: owner, identityClaim: "sub", ownerField: "owner"}, { allow: private, provider: iam }]) {
  id: ID!
  firstname: String!
  lastname: String!
  email: String!
  telephone: String!
  claimed: Boolean
  data: String
  status: GenericStatus
  carts: [Cart] @hasMany(indexName: "byCartClient", fields: ["id"])
  owner: String
}

type Cart @model @auth(rules: [{ allow: owner, identityClaim: "sub", ownerField: "owner"}, { allow: private, provider: iam }]) {
  id: ID!
  buildingID: ID! @index(name: "byCartBuilding", sortKeyFields: ["id"])
  userID: ID @index(name: "byCartUser", sortKeyFields: ["id"])
  clientID: ID @index(name: "byCartClient", sortKeyFields: ["id"])
  savedCart: String!
  building: Building @belongsTo(fields: ["buildingID"])
  user: User @belongsTo(fields: ["userID"])
  client: Client @belongsTo(fields: ["clientID"])
  owner: String
}

amrogad avatar Aug 18 '25 14:08 amrogad

@amrogad Did you enable conflict detection by any chance? I don't see createdAt and updatedAt in any of your models but you reference failures on these veriables. These are automatically added when conflict detection is enabled.

tylerjroach avatar Aug 18 '25 14:08 tylerjroach

@amrogad Did you enable conflict detection by any chance? I don't see createdAt and updatedAt in any of your models but you reference failures on these veriables. These are automatically added when conflict detection is enabled.

Nope it is not enabled, I also added the full error message for clarity

amrogad avatar Aug 18 '25 15:08 amrogad

Hello @amrogad, so far I've been unable to reproduce the issue. I populated all of the tables with data before adding price_adjustments then after updating the schema I queried the relevant tables afterwards and confirmed that price_adjustments was returned as null. Could you please provide a code snippet of the GraphQL request that is resulting in this error.

tyllark avatar Aug 21 '25 02:08 tyllark

Here's the GraphQL mutation and input used when I get this error:

mutation UpdateCart($input: UpdateCartInput!, $condition: ModelCartConditionInput) {
  updateCart(input: $input, condition: $condition) {
    id
    savedCart
    owner
    createdAt
    updatedAt
    building {
      id
      name
      status
      data
      users
      notes
      createdAt
      updatedAt
    }
    buildingID
    user {
      id
      email
      status
      data
      createdAt
      updatedAt
      owner
    }
    userID
    client {
      id
      firstname
      lastname
      email
      telephone
      claimed
      data
      status
      owner
      createdAt
      updatedAt
    }
    clientID
    owner
  }
}
{
  "input": {
    "id": "9a3b3401-d148-4727-85fe-f263b6c85620",
    "savedCart": [
      {
        "uuid": "",
        "price": 392.0,
        "building_name": "TestRevertAmplify",
        "window_id": "0aee3cc5-b1b6-46a8-bb03-4a250ac2c3b9",
        "window_name": "Window #1",
        "window_height": "1371.6",
        "window_width": "1143",
        "category_name": "Shangri-La",
        "window_data": "{\"measurements\":{\"width\":\"1143\",\"height\":\"1371.6\",\"horizontalOffset\":\"-6.35\",\"verticalOffset\":\"0\",\"blind_width\":\"1136.65\",\"blind_height\":\"1371.6\"},\"mountType\":\"insideFrame\",\"controlPosition\":\"right\",\"validated\":false}",
        "window_createdAt": "2025-07-22T02:56:49.645000000Z",
        "window_status": "",
        "status": "",
        "room_id": "9b09c96f-516b-44fb-956b-5c37b7d08df5",
        "room_name": "Basement",
        "configuration": {
          "window_depth": "",
          "window_horizontalOffset": "-6.35",
          "window_verticalOffset": "0",
          "blind_height": "1371.6",
          "blind_width": "1136.65",
          "mount_type": "insideFrame",
          "control_position": "right",
          "is_split_window": false,
          "products_snapshot": []
        },
        "notes": "",
        "window_notes": "",
        "products": {
          "product": "0b29515f-3cc9-4e02-87e8-f979e76c553d",
          "priceChart": "8ac8384c-79af-45f2-950a-eb0fe023e7e2",
          "optionalProducts": [
            { "product": "e0f7b918-9b40-45cf-98b0-1605ebb8b452", "quantity": 0 },
            { "product": "a001148e-6c38-44a5-9fe4-cc81748ecf47", "quantity": 0 },
            { "product": "33fd3a64-f453-4221-ab2a-4a577e5ef25a", "quantity": 0 }
          ]
        }
      }
    ],
    "owner": "9edf5e13-2951-45b5-9184-b745c55de91e",
    "buildingID": "c9c91970-becb-48ff-81bd-3bf6b3cfba6",
    "clientID": "e588d172-d91f-43ff-a579-722d4dd3155d"
  }
}

amrogad avatar Aug 22 '25 02:08 amrogad

Hello @amrogad, how are you generating the GraphQL document and input? On my side I'm running the following without any errors:

final updateRequest = ModelMutations.update(updatedCart);
final updateResponse = await Amplify.API.mutate(request: updateRequest).response;

Your document seems to have an extra owner in user and at the root level. When I run ModelMutations.update(updatedCart).document; I get the following:

mutation updateCart($input: UpdateCartInput!, $condition:  ModelCartConditionInput) { 
	updateCart(input: $input, condition: $condition) { 
		id 
		savedCart 
		owner 
		createdAt 
		updatedAt 
		building { 
			id
			name
			status
			data
			users
			notes
			createdAt
			updatedAt 
		} 
		buildingID 
		user { 
			id 
			email 
			status 
			data 
			createdAt 
			updatedAt 
		} 
		userID 
		client { 
			id
			firstname 
			lastname 
			email 
			telephone 
			claimed 
			data 
			status 
			owner 
			createdAt 
			updatedAt 
		} 
		clientID 
	}
}

Your input also seems to be in a completely format from mine, which may be the source of your null fields. I get the following when I run ModelMutations.update(updatedCart).variables; I get the following:

{
  "input": {
    "id": "f39cbfc4-f80e-499f-a50f-a277a6eda9af",
    "savedCart": "savedCart 1",
    "owner": "owner 1",
    "buildingID": "91c4da05-d998-4728-a4bc-3d3391863c86",
    "userID": "f2cf3253-6c23-424e-9301-79f2b83dd2ed",
    "clientID": "96687c21-7761-4056-9817-df30464e6c17"
  },
  "condition": null
}

tyllark avatar Aug 22 '25 22:08 tyllark

@tyllark Is it possible to share the code privately on discord?

amrogad avatar Aug 25 '25 18:08 amrogad

Hello, Please open a ticket with AWS customer support and share the relevant details of your project (code snippets/steps to reproduce/schema details), if it's not possible to share here.

thisisabhash avatar Aug 27 '25 16:08 thisisabhash