Page updated Jan 16, 2024

Amplify Codegen Models - List and list components nullability

What is changing?

Amplify CLI 5.1.2 has updated the codegen process to correctly set the optionality for types in models.

Schema example

1type ListStringContainer @model {
2 requiredElementRequiredList: [String!]!
3 requiredElementOptionalList: [String!]
4 optionalElementRequiredList: [String]!
5 optionalElementOptionalList: [String]
6}

In this example, there are four fields with different combinations of optionality:

  • requiredElementRequiredList - the list itself is required. Elements it contains must be non-null. Empty lists can only be represented as an empty array.
  • requiredElementOptionalList - the list itself is optional. If present, elements it contains must be non-null. Empty lists could be represented as either an empty array or a null field.
  • optionalElementRequiredList - the list itself is required. If present, elements it contains may be null.
  • optionalElementOptionalList - the list itself is optional. If present, elements it contains may be null.

The list component in this example is a String type, however, this applies for other types as well such as Int, Bool, and embedded types that you define yourself.

Why are we introducing this change?

This is to align the optionality of the generated Swift models as closely as possible to the type defined in the schema.

Who is impacted?

Developers building an iOS app with Amplify DataStore or Amplify API generates Swift models by running the command amplify codegen models.

Previous generated Swift code

1public struct ListStringContainer: Model {
2 public var requiredElementRequiredList: [String]
3 public var requiredElementOptionalList: [String]
4 public var optionalElementRequiredList: [String]?
5 public var optionalElementOptionalList: [String]?
6 ...
7}

Current code generated Swift code

1public struct ListStringContainer: Model {
2 public var requiredElementRequiredList: [String]
3 public var requiredElementOptionalList: [String]?
4 public var optionalElementRequiredList: [String?]
5 public var optionalElementOptionalList: [String?]?
6 ...
7}

The difference between the current and previous code:

  • requiredElementRequiredList - No changes
  • requiredElementOptionalList - the list was required and is now optional.
  • optionalElementRequiredList - the list component was required and is now optional. The list was optional and is now required
  • optionalElementOptionalList - the list component was required and is now optional.

When do I have to upgrade?

This is behind a feature flag in Amplify CLI 5.1.2 and will be deprecated by November 1st, 2021. Developers with existing apps should upgrade to the latest CLI, set the feature flag, and update their app code or their schema (see recommendations following) to account for the change in optionality of the types. Developers building a new app will automatically generate code with the latest changes and no action is required.

Where do I make these changes?

  1. Update Amplify CLI to the latest version
1amplify upgrade
  1. The version should be at least 5.1.2
1amplify --v # at least 5.1.2
  1. If building an existing app, set the feature flag handleListNullabilityTransparently to true in cli.json at the amplify project root.

  2. Run amplify codegen models to generate the latest models.

  3. Open the App and make sure the app compiles with the latest generated models. Depending on your schema, you may be in the following scenarios.

Scenario 1. Schema: requiredElementOptionalList: [String!]

1// Previous - Swift type
2public var requiredElementOptionalList: [String]
3
4// Current - Swift type
5public var requiredElementOptionalList: [String]?
6
7// Previous - Code
8print(container.requiredElementOptionalList) // ["value1", "value2"]
9
10// Current - Code
11if let requiredElementList = container.requiredElementOptionalList {
12 print(requiredElementList) // ["value1", "value2"]
13}

Since the list was required and is now optional, unwrap the optional to retrieve the values.

Recommendation: Update the type in the schema from [String!] to[String!]! to make the list required if you do not have an app use case for storing a null list. This will remove the need to unwrap the list in code.

Scenario 2. Schema: optionalElementRequiredList: [String]!

1// Previous - Swift type
2public var optionalElementRequiredList: [String]?
3
4// Current - Swift type
5public var optionalElementRequiredList: [String?]
6
7// Previous - Code
8if let optionalElementRequiredList = container.optionalElementRequiredList {
9 print(optionalElementRequiredList) // ["value1", "value2"]
10 for value in optionalElementRequiredList {
11 print(value) // "value1", "value2
12 }
13}
14
15// After
16print(container.optionalElementRequiredList) // [Optional("value1"), Optional("value2")]
17for value in container.optionalElementRequiredList {
18 if let value = value {
19 print(value) // "value1", "value2
20 }
21}

Since the list component was required and is now optional, unwrap the optional value to retrieve the value. The list was optional and is now required, remove any unwrapping done for the list.

Recommendation: Update the type in the schema from [String]! to [String!]! to make the list component required if you do not store null values in the list. This will remove the need to unwrap the list component in code.

Scenario 3. Schema: optionalElementOptionalList: [String]

1// Previous - Swift type
2public var optionalElementOptionalList: [String]?
3
4// Current - Swift type
5public var optionalElementOptionalList: [String?]?
6
7// Previous - Code
8if let optionalElementOptionalList = container.optionalElementOptionalList zzz
9 print(optionalElementOptionalList) // ["value1", "value2"]
10 for value in optionalElementOptionalList {
11 print(value) // "value1", "value2
12 }
13}
14
15// After
16if let optionalElementList = container.optionalElementOptionalList {
17 print(optionalElementList) // [Optional("value1"), Optional("value2")]
18 for value in optionalElementList {
19 if let value = value {
20 print(value) // "value1", "value2
21 }
22 }
23}

Since the list component was required and is now optional, unwrap the optional value to retrieve the value.

Recommendation: Update the type in the schema from [String] to [String!]! to make the list and list component required if you do not store null values in the list or a null list. This will remove the need to unwrap the list and the list components.