Client code generation
Codegen helps you generate native code for iOS and Android, as well as the generation of types for Flow and TypeScript. It can also generate GraphQL statements (queries, mutations, and subscriptions) so that you don't have to hand code them.
Codegen add
workflow triggers automatically when an AppSync API is pushed to the cloud. You will be prompted if you want to configure codegen when an AppSync API is created and if you opt-in for codegen, subsequent pushes prompt you if they want to update the generated code after changes get pushed to the cloud.
When a project is configured to generate code with codegen, it stores all the configuration .graphqlconfig.yml
file in the root folder of your project. When generating types, codegen uses GraphQL statements as input. It will generate only the types that are being used in the GraphQL statements.
Statement depth
In the below schema there are connections between Comment
-> Post
-> Blog
-> Post
-> Comments
. When generating statements codegen has a default limit of 2 for depth traversal. But if you need to go deeper than 2 levels you can change the maxDepth
parameter either when setting up your codegen or by passing --maxDepth
parameter to codegen
type Blog @model { id: ID! name: String! posts: [Post] @connection(name: "BlogPosts")}type Post @model { id: ID! title: String! blog: Blog @connection(name: "BlogPosts") comments: [Comment] @connection(name: "PostComments")}type Comment @model { id: ID! content: String post: Post @connection(name: "PostComments")}
query GetComment($id: ID!) { getComment(id: $id) { # depth level 1 id content post { # depth level 2 id title blog { # depth level 3 id name posts { # depth level 4 items { # depth level 5 id title } nextToken } } comments { # depth level 3 items { # depth level 4 id content post { # depth level 5 id title } } nextToken } } }}
General Usage
amplify add codegen
amplify add codegen
The amplify add codegen
allows you to add AppSync API created using the AWS console. If you have your API is in a different region then that of your current region, the command asks you to choose the region. If you are adding codegen outside of an initialized amplify project, provide your introspection schema named schema.json
in the same directory that you make the add codegen call from. Note: If you use the --apiId flag to add an externally created AppSync API, such as one created in the AWS console, you will not be able to manage this API from the Amplify CLI with commands such as amplify api update when performing schema updates. You cannot add an external AppSync API when outside of an initialized project.
amplify configure codegen
amplify configure codegen
The amplify configure codegen
command allows you to update the codegen configuration after it is added to your project. When outside of an initialized project, you can use this to update your project configuration as well as the codegen configuration.
amplify codegen statements
amplify codegen statements [--nodownload] [--maxDepth <int>]
The amplify codegen statements
command generates GraphQL statements(queries, mutation and subscription) based on your GraphQL schema. This command downloads introspection schema every time it is run, but it can be forced to use previously downloaded introspection schema by passing --nodownload
flag.
amplify codegen types
amplify codegen types
The amplify codegen types [--nodownload]
command generates GraphQL types
for Flow and typescript and Swift class in an iOS project. This command downloads introspection schema every time it is run, but it can be forced to use previously downloaded introspection schema by passing --nodownload
flag.
amplify codegen
amplify codegen [--maxDepth <int>]
The amplify codegen [--nodownload]
generates GraphQL statements
and types
. This command downloads introspection schema every time it is run but it can be forced to use previously downloaded introspection schema by passing --nodownload
flag. If you are running codegen outside of an initialized amplify project, the introspection schema named schema.json
must be in the same directory that you run amplify codegen from. This command will not download the introspection schema when outside of an amplify project - it will only use the introspection schema provided.
Workflows
The design of codegen functionality provides mechanisms to run at different points in your app development lifecycle, including when you create or update an API as well as independently when you want to just update the data fetching requirements of your app but leave your API alone. It additionally allows you to work in a team where the schema is updated or managed by another person. Finally, you can also include the codegen in your build process so that it runs automatically (such as from in Xcode).
Flow 1: Create API then automatically generate code
amplify initamplify add api (select GraphQL)amplify push
You’ll see questions as before, but now it will also automatically ask you if you want to generate GraphQL statements and do codegen. It will also respect the ./app/src/main
directory for Android projects. After the AppSync deployment finishes the Swift file will be automatically generated (Android you’ll need to kick off a Gradle Build step) and you can begin using in your app immediately.
Flow 2: Modify GraphQL schema, push, then automatically generate code
During development, you might wish to update your GraphQL schema and generated code as part of an iterative dev/test cycle. Modify & save your schema in amplify/backend/api/<apiname>/schema.graphql
then run:
amplify push
Each time you will be prompted to update the code in your API and also ask you if you want to run codegen again as well, including regeneration of the GraphQL statements from the new schema.
Flow 3: No API changes, just update GraphQL statements & generate code
One of the benefits of GraphQL is the client can define it's data fetching requirements independently of the API. Amplify codegen supports this by allowing you to modify the selection set (e.g. add/remove fields inside the curly braces) for the GraphQL statements and running type generation again. This gives you fine-grained control over the network requests that your application is making. Modify your GraphQL statements (default in the ./graphql
folder unless you changed it) then save the files and run:
amplify codegen types
A new updated Swift file will be created (or run Gradle Build on Android for the same). You can then use the updates in your application code.
Flow 4: Shared schema, modified elsewhere (e.g. console or team workflows)
Suppose you are working in a team and the schema is updated either from the AWS AppSync console or on another system. Your types are now out of date because your GraphQL statement was generated off an outdated schema. The easiest way to resolve this is to regenerate your GraphQL statements, update them if necessary, and then generate your types again. Modify the schema in the console or on a separate system, then run:
amplify codegen statementsamplify codegen types
You should have newly generated GraphQL statements and Swift code that matches the schema updates. If you ran the second command your types will be updated as well. Alternatively, if you run amplify codegen
alone it will perform both of these actions.
Flow 5: Introspection Schema outside of an initialized project
If you would like to generate statements and types without initializing an amplify project, you can do so by providing your introspection schema named schema.json
in your project directory and adding codegen from the same directory. To download your introspection schema from an AppSync api, in the AppSync console go to the schema editor and under "Export schema" choose schema.json
.
amplify add codegen
Once codegen has been added you can update your introspection schema, then generate statements and types again without re-entering your project information.
amplify codegen
You can update your project and codegen configuration if required.
amplify configure codegenamplify codegen
iOS usage
This section will walk through the steps needed to take an iOS project written in Swift and add Amplify to it along with a GraphQL API using AWS AppSync. If you are a first time user, we recommend starting with a new Xcode project and a single View Controller.
Setup
After completing the Amplify Getting Started navigate in your terminal to an Xcode project directory and run the following:
amplify init ## Select iOS as your platformamplify add api ## Select GraphQL, API key, "Single object with fields Todo application"amplify push ## Sets up backend and prompts you for codegen, accept the defaults
The add api
flow above will ask you some questions, like if you already have an annotated GraphQL schema. If this is your first time using the CLI select No and let it guide you through the default project "Single object with fields (e.g., “Todo” with ID, name, description)" as it will be used in the code generation examples below. Later on, you can always change it.
Since you added an API the amplify push
process will automatically prompt you to enter the codegen process and walk through the configuration options. Accept the defaults and it will create a file named API.swift
in your root directory (unless you choose to name it differently) as well as a directory called graphql
with your documents. You also will have an awsconfiguration.json
file that the AppSync client will use for initialization.
Next, modify your Podfile with a dependency of the AWS AppSync SDK:
target 'PostsApp' do use_frameworks! pod 'AWSAppSync'end
Run pod install
from your terminal and open up the *.xcworkspace
Xcode project. Add the API.swift
and awsconfiguration.json
files to your project (File->Add Files to ..->Add) and then build your project ensuring there are no issues.
Initialize the AppSync client
Inside your application delegate is the best place to initialize the AppSync client. The AWSAppSyncServiceConfig
represents the configuration information present in awsconfiguration.json file. By default, the information under the Default
section will be used. You will need to create an AWSAppSyncClientConfiguration
and AWSAppSyncClient
like below:
import AWSAppSync
@UIApplicationMainclass AppDelegate: UIResponder, UIApplicationDelegate {
var appSyncClient: AWSAppSyncClient?
func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool {
do { // You can choose your database location if you wish, or use the default let cacheConfiguration = try AWSAppSyncCacheConfiguration()
// AppSync configuration & client initialization let appSyncConfig = try AWSAppSyncClientConfiguration( appSyncServiceConfig: AWSAppSyncServiceConfig(), cacheConfiguration: cacheConfiguration ) appSyncClient = try AWSAppSyncClient(appSyncConfig: appSyncConfig) } catch { print("Error initializing appsync client. \(error)") } // other methods return true }
Next, in your application code where you wish to use the AppSync client, such in a Todos
class which is bound to your View Controller, you need to reference this in the viewDidLoad()
lifecycle method:
import AWSAppSync
class Todos: UIViewController{ //Reference AppSync client var appSyncClient: AWSAppSyncClient?
override func viewDidLoad() { super.viewDidLoad() //Reference AppSync client from App Delegate let appDelegate = UIApplication.shared.delegate as! AppDelegate appSyncClient = appDelegate.appSyncClient }}
Queries
Now that the backend is configured, you can run a GraphQL query. The syntax is appSyncClient?.fetch(query: <NAME>Query() {(result, error)})
where <NAME>
comes from the GraphQL statements that amplify codegen types
created. For example, if you have a ListTodos
query your code will look like the following:
//Run a queryappSyncClient?.fetch(query: ListTodosQuery()) { (result, error) in if error != nil { print(error?.localizedDescription ?? "") return } result?.data?.listTodos?.items!.forEach { print(($0?.name)! + " " + ($0?.description)!) }}
Optionally, you can set a cache policy on the query like so:
appSyncClient?.fetch(query: ListTodosQuery(), cachePolicy: .returnCacheDataAndFetch) { (result, error) in
returnCacheDataAndFetch
will pull results from the local cache first before retrieving data over the network. This gives a snappy UX as well as offline support.
Mutations
For adding data now you will need to run a GraphQL mutation. The syntax appSyncClient?.perform(mutation: <NAME>Mutation() {(result, error)})
where <NAME>
comes from the GraphQL statements that amplify codegen types
created. However, most GraphQL schemas organize mutations with an input
type for maintainability, which is what the Amplify CLI does as well. Therefore you'll pass this as a parameter called input
as in the example below:
let mutationInput = CreateTodoInput(name: "Use AppSync", description:"Realtime and Offline")
appSyncClient?.perform( mutation: CreateTodoMutation(input: mutationInput)) { (result, error) in if let error = error as? AWSAppSyncClientError { print("Error occurred: \(error.localizedDescription )") } if let resultError = result?.errors { print("Error saving the item on server: \(resultError)") return }}
Subscriptions
Finally it's time to setup a subscription to realtime data. The syntax appSyncClient?.subscribe(subscription: <NAME>Subscription() {(result, transaction, error)})
where <NAME>
comes from the GraphQL statements that amplify codegen types
created.
// Subscription notifications will only be delivered as long as this is retainedvar subscriptionWatcher: Cancellable?
//In your app codedo { subscriptionWatcher = try appSyncClient?.subscribe( subscription: OnCreateTodoSubscription(), resultHandler: { (result, transaction, error) in if let result = result { print(result.data!.onCreateTodo!.name + " " + result.data!.onCreateTodo!.description!) } else if let error = error { print(error.localizedDescription) } } )} catch { print("Error starting subscription.")}
Subscriptions can also take input
types like mutations, in which case they will be subscribing to particular events based on the input. Learn more about Subscription arguments in AppSync here.
Complete Sample
AppDelegate.swift
import UIKitimport AWSAppSync
@UIApplicationMainclass AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow? var appSyncClient: AWSAppSyncClient?
func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { do { // You can choose your database location if you wish, or use the default let cacheConfiguration = try AWSAppSyncCacheConfiguration()
// AppSync configuration & client initialization let appSyncConfig = try AWSAppSyncClientConfiguration(appSyncServiceConfig: AWSAppSyncServiceConfig(), cacheConfiguration: cacheConfiguration) appSyncClient = try AWSAppSyncClient(appSyncConfig: appSyncConfig) } catch { print("Error initializing appsync client. \(error)") } return true }}
ViewController.swift
import UIKitimport AWSAppSync
class ViewController: UIViewController {
var appSyncClient: AWSAppSyncClient?
// Subscription notifications will only be delivered as long as this is retained var subscriptionWatcher: Cancellable?
override func viewDidLoad() { super.viewDidLoad() let appDelegate = UIApplication.shared.delegate as! AppDelegate appSyncClient = appDelegate.appSyncClient
// Note: each of these are asynchronous calls. Attempting to query the results of `runMutation` immediately // after calling it probably won't work--instead, invoke the query in the mutation's result handler runMutation() runQuery() subscribe() }
func subscribe() { do { subscriptionWatcher = try appSyncClient?.subscribe(subscription: OnCreateTodoSubscription()) { // The subscription watcher's result block retains a strong reference to the result handler block. // Make sure to capture `self` weakly if you use it // [weak self] (result, transaction, error) in if let result = result { print(result.data!.onCreateTodo!.name + " " + result.data!.onCreateTodo!.description!) // Update the UI, as in: // self?.doSomethingInTheUIWithSubscriptionResults(result) // By default, `subscribe` will invoke its subscription callbacks on the main queue, so there // is no need to dispatch to the main queue. } else if let error = error { print(error.localizedDescription) } } } catch { print("Error starting subscription.") } }
func runMutation(){ let mutationInput = CreateTodoInput(name: "Use AppSync", description:"Realtime and Offline") appSyncClient?.perform(mutation: CreateTodoMutation(input: mutationInput)) { (result, error) in if let error = error as? AWSAppSyncClientError { print("Error occurred: \(error.localizedDescription )") } if let resultError = result?.errors { print("Error saving the item on server: \(resultError)") return } // The server and the local cache are now updated with the results of the mutation } }
func runQuery(){ appSyncClient?.fetch(query: ListTodosQuery()) {(result, error) in if error != nil { print(error?.localizedDescription ?? "") return } result?.data?.listTodos?.items!.forEach { print(($0?.name)! + " " + ($0?.description)!) } } }}
Android usage
This section will walk through the steps needed to take an Android Studio project written in Java and add Amplify to it along with a GraphQL API using AWS AppSync. If you are a first time user, we recommend starting with a new Android Studio project and a single Activity class.
Setup
After completing the Amplify Getting Started navigate in your terminal to an Android Studio project directory and run the following:
amplify init ## Select iOS as your platformamplify add api ## Select GraphQL, API key, "Single object with fields Todo application"amplify push ## Sets up backend and prompts you for codegen, accept the defaults
The add api
flow above will ask you some questions, like if you already have an annotated GraphQL schema. If this is your first time using the CLI select No and let it guide you through the default project "Single object with fields (e.g., “Todo” with ID, name, description)" as it will be used in the code generation examples below. Later on, you can always change it.
Since you added an API the amplify push
process will automatically enter the codegen process and prompt you for configuration. Accept the defaults and it will create a file named awsconfiguration.json
in the ./app/src/main/res/raw
directory that the AppSync client will use for initialization. To finish off the build process there are Gradle and permission updates needed.
First, in the project's build.gradle
, add the following dependency in the build script:
classpath 'com.amazonaws:aws-android-sdk-appsync-gradle-plugin:2.6.+'
Next, in the app's build.gradle
add in a plugin of apply plugin: 'com.amazonaws.appsync'
and a dependency of implementation 'com.amazonaws:aws-android-sdk-appsync:2.6.+'
. For example:
apply plugin: 'com.android.application'apply plugin: 'com.amazonaws.appsync'android { // Typical items}dependencies { // Typical dependencies implementation 'com.amazonaws:aws-android-sdk-appsync:2.6.+' implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.0' implementation 'org.eclipse.paho:org.eclipse.paho.android.service:1.1.1'}
Finally, update your AndroidManifest.xml
with updates to <uses-permissions>
for network calls and offline state. Also add a <service>
entry under <application>
for MqttService
for subscriptions:
<uses-permission android:name="android.permission.INTERNET"/><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/><uses-permission android:name="android.permission.WAKE_LOCK" /><uses-permission android:name="android.permission.READ_PHONE_STATE" /><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<!--other code-->
<application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme">
<service android:name="org.eclipse.paho.android.service.MqttService" />
<!--other code--> </application>
Build your project ensuring there are no issues.
Initialize the AppSync client
Inside your application code, such as the onCreate()
lifecycle method of your activity class, you can initialize the AppSync client using an instance of AWSConfiguration()
in the AWSAppSyncClient
builder. This reads configuration information present in the awsconfiguration.json
file. By default, the information under the Default section will be used.
private AWSAppSyncClient mAWSAppSyncClient;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mAWSAppSyncClient = AWSAppSyncClient.builder() .context(getApplicationContext()) .awsConfiguration(new AWSConfiguration(getApplicationContext())) .build(); }
Queries
Now that the backend is configured, you can run a GraphQL query. The syntax of the callback is GraphQLCall.Callback<{NAME>Query.Data>
where {NAME}
comes from the GraphQL statements that amplify codegen types
created. You will invoke this from an instance of the AppSync client with a similar syntax of .query(<NAME>Query.builder().build())
. For example, if you have a ListTodos
query your code will look like the following:
public void query(){ mAWSAppSyncClient.query(ListTodosQuery.builder().build()) .responseFetcher(AppSyncResponseFetchers.CACHE_AND_NETWORK) .enqueue(todosCallback); }
private GraphQLCall.Callback<ListTodosQuery.Data> todosCallback = new GraphQLCall.Callback<ListTodosQuery.Data>() { @Override public void onResponse(@Nonnull Response<ListTodosQuery.Data> response) { Log.i("Results", response.data().listTodos().items().toString()); }
@Override public void onFailure(@Nonnull ApolloException e) { Log.e("ERROR", e.toString()); } };
You can optionally change the cache policy on AppSyncResponseFetchers
but we recommend leaving CACHE_AND_NETWORK
as it will pull results from the local cache first before retrieving data over the network. This gives a snappy UX as well as offline support.
Mutations
For adding data now you will need to run a GraphQL mutation. The syntax of the callback is GraphQLCall.Callback<{NAME}Mutation.Data>
where {NAME}
comes from the GraphQL statements that amplify codegen types
created. However, most GraphQL schemas organize mutations with an input
type for maintainability, which is what the Amplify CLI does as well. Therefore you'll pass this as a parameter called input
created with a second builder. You will invoke this from an instance of the AppSync client with a similar syntax of .mutate({NAME}Mutation.builder().input({Name}Input).build())
like so:
public void mutation(){ CreateTodoInput createTodoInput = CreateTodoInput.builder(). name("Use AppSync"). description("Realtime and Offline"). build();
mAWSAppSyncClient.mutate(CreateTodoMutation.builder().input(createTodoInput).build()) .enqueue(mutationCallback);}
private GraphQLCall.Callback<CreateTodoMutation.Data> mutationCallback = new GraphQLCall.Callback<CreateTodoMutation.Data>() { @Override public void onResponse(@Nonnull Response<CreateTodoMutation.Data> response) { Log.i("Results", "Added Todo"); }
@Override public void onFailure(@Nonnull ApolloException e) { Log.e("Error", e.toString()); }};
Subscriptions
Finally, it's time to set up a subscription to real-time data. The callback is just AppSyncSubscriptionCall.Callback
and you invoke it with a client .subscribe()
call and pass in a builder with the syntax of {NAME}Subscription.builder()
where {NAME}
comes from the GraphQL statements that amplify codegen types
created. Note that the Amplify GraphQL transformer has a common nomenclature of putting the word On
in front of a subscription like the below example:
private AppSyncSubscriptionCall subscriptionWatcher;
private void subscribe(){ OnCreateTodoSubscription subscription = OnCreateTodoSubscription.builder().build(); subscriptionWatcher = mAWSAppSyncClient.subscribe(subscription); subscriptionWatcher.execute(subCallback); }
private AppSyncSubscriptionCall.Callback subCallback = new AppSyncSubscriptionCall.Callback() { @Override public void onResponse(@Nonnull Response response) { Log.i("Response", response.data().toString()); }
@Override public void onFailure(@Nonnull ApolloException e) { Log.e("Error", e.toString()); }
@Override public void onCompleted() { Log.i("Completed", "Subscription completed"); } };
Subscriptions can also take input
types like mutations, in which case they will be subscribing to particular events based on the input. Learn more about Subscription arguments in AppSync here.
Sample
MainActivity.java
import android.util.Log;import com.amazonaws.mobile.config.AWSConfiguration;import com.amazonaws.mobileconnectors.appsync.AWSAppSyncClient;import com.amazonaws.mobileconnectors.appsync.AppSyncSubscriptionCall;import com.amazonaws.mobileconnectors.appsync.fetcher.AppSyncResponseFetchers;import com.apollographql.apollo.GraphQLCall;import com.apollographql.apollo.api.Response;import com.apollographql.apollo.exception.ApolloException;import javax.annotation.Nonnull;import amazonaws.demo.todo.CreateTodoMutation;import amazonaws.demo.todo.ListTodosQuery;import amazonaws.demo.todo.OnCreateTodoSubscription;import amazonaws.demo.todo.type.CreateTodoInput;
public class MainActivity extends AppCompatActivity {
private AWSAppSyncClient mAWSAppSyncClient; private AppSyncSubscriptionCall subscriptionWatcher;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mAWSAppSyncClient = AWSAppSyncClient.builder() .context(getApplicationContext()) .awsConfiguration(new AWSConfiguration(getApplicationContext())) .build(); query(); mutation(); subscribe(); }
private void subscribe(){ OnCreateTodoSubscription subscription = OnCreateTodoSubscription.builder().build(); subscriptionWatcher = mAWSAppSyncClient.subscribe(subscription); subscriptionWatcher.execute(subCallback); }
private AppSyncSubscriptionCall.Callback subCallback = new AppSyncSubscriptionCall.Callback() { @Override public void onResponse(@Nonnull Response response) { Log.i("Response", response.data().toString()); }
@Override public void onFailure(@Nonnull ApolloException e) { Log.e("Error", e.toString()); }
@Override public void onCompleted() { Log.i("Completed", "Subscription completed"); } };
public void query(){ mAWSAppSyncClient.query(ListTodosQuery.builder().build()) .responseFetcher(AppSyncResponseFetchers.CACHE_AND_NETWORK) .enqueue(todosCallback); }
private GraphQLCall.Callback<ListTodosQuery.Data> todosCallback = new GraphQLCall.Callback<ListTodosQuery.Data>() { @Override public void onResponse(@Nonnull Response<ListTodosQuery.Data> response) { Log.i("Results", response.data().listTodos().items().toString()); }
@Override public void onFailure(@Nonnull ApolloException e) { Log.e("ERROR", e.toString()); } };
public void mutation(){
CreateTodoInput createTodoInput = CreateTodoInput.builder(). name("Use AppSync"). description("Realtime and Offline"). build();
mAWSAppSyncClient.mutate(CreateTodoMutation.builder().input(createTodoInput).build()) .enqueue(mutationCallback);
}
private GraphQLCall.Callback<CreateTodoMutation.Data> mutationCallback = new GraphQLCall.Callback<CreateTodoMutation.Data>() { @Override public void onResponse(@Nonnull Response<CreateTodoMutation.Data> response) { Log.i("Results", "Added Todo"); }
@Override public void onFailure(@Nonnull ApolloException e) { Log.e("Error", e.toString()); } };}