Amplify project setup
Before you begin, make sure you have the following installed:
- Android Studio version 4.0 or higher
- Android SDK API level 16 (Jelly Bean) or higher
Configure the Amplify CLI
To set up the Amplify CLI on your local machine, you have to configure it to connect to your AWS account.
Configure Amplify by running the following command:
amplify configure
amplify configure
will ask you to sign into the AWS Console.
Once you're signed in, Amplify CLI will ask you to use the AWS Identity and Access Management (IAM) to create an IAM user.
Specify the AWS Region? region: # Your preferred regionFollow the instructions athttps://docs.amplify.aws/cli/start/install/#configure-the-amplify-cli
to complete the user creation in the AWS consolehttps://console.aws.amazon.com/iamv2/home#/users/create
Navigate to the IAM User creation page if it's not already open.
Enter a User name and select Next. You can name the user anything but we'll call it "amplify-dev".
Select Attach policies directly and select AdministratorAccess-Amplify as the Permissions policy. Select Next.
On the Review page, check that everything looks good and select Create user.
This will redirect to the users list page. Select the user you just created.
On the user details page, navigate to the Security credentials tab, scroll down to Access keys and select Create access keys.
On the next page, select Command Line Interface, acknowledge the warning, and select Next.
On the next page select Create access key. You'll then see a page with the access keys for the user. Use the copy icon to copy these values to your clipboard, then return to the Amplify CLI.
Enter the values you just copied into the corresponding CLI prompts.
Enter the access key of the newly created user:? accessKeyId: # YOUR_ACCESS_KEY_ID? secretAccessKey: # YOUR_SECRET_ACCESS_KEYThis would update/create the AWS Profile in your local machine? Profile Name: # (default)
Successfully set up the new user.
Manually configure the Amplify CLI
If you are using an IAM role or IAM Identity Center (previously AWS SSO), you can configure your local machine for use with Amplify CLI by creating AWS profile entries manually rather than the amplify configure
wizard.
To create an AWS profile locally using IAM Identity Center, you can use the AWS CLI wizard, aws configure sso
, or write to ~/.aws/config
directly:
[profile my-sso-profile]sso_session = my-ssosso_account_id = 123456789011sso_role_name = AdministratorAccess-Amplifyregion = us-west-2output = json
[sso-session my-sso]sso_region = us-east-1sso_start_url = https://my-sso-portal.awsapps.com/startsso_registration_scopes = sso:account:access
Currently, the Amplify CLI requires a workaround for use with IAM Identity Center due to an issue in how it resolves credentials.
[profile my-sso-profile]sso_session = my-ssosso_account_id = 123456789011sso_role_name = AdministratorAccess-Amplifyregion = us-west-2output = json+ credential_process = aws configure export-credentials --profile my-sso-profile[sso-session my-sso]sso_region = us-east-1sso_start_url = https://my-sso-portal.awsapps.com/startsso_registration_scopes = sso:account:access
Using the example above, when creating a new app or pulling an existing app, specify my-sso-profile
as the AWS profile you'd like to use with the Amplify app.
To create an AWS profile locally using an IAM role, assign the AdministratorAccess-Amplify
permissions set to the role and set the role in your ~/.aws/config
file:
[profile amplify-admin]role_arn = arn:aws:iam::123456789012:role/amplify-adminsource_profile = amplify-user
[profile amplify-user]region=us-east-1
Using the example above, when creating a new app or pulling an existing app, specify amplify-admin
as the AWS profile you'd like to use with the Amplify app
Next, you'll set up the app and initialize Amplify!
Create your application
For this section you will set up a skeleton project so that Amplify categories can be added to it.
1. Create a new project
Open Android Studio. Select + Create New Project.
In Select a Project Template, select Empty Activity. Press Next.
Next, configure your project:
- Enter MyAmplifyApp in the Name field
- Select either Java or Kotlin from the Language dropdown menu
- Select API 16: Android 4.1 (Jelly Bean) from the Minimum SDK dropdown menu
- Press Finish
Android Studio will open your project with a tab opened to either MainActivity.java or MainActivity.kt depending upon if you created a Java or Kotlin project respectively.
You now have an empty Android project into which you'll add Amplify in the next steps.
2. Install Amplify Libraries
Amplify for Android is distributed as Apache Maven packages. In this section, you'll add the packages and other required directives to your build configuration.
Under Gradle Scripts, open build.gradle (Module :app).
Add the following lines:
android { compileOptions { // Support for Java 8 features coreLibraryDesugaringEnabled true sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 }}
dependencies { // Amplify core dependency implementation 'com.amplifyframework:core:ANDROID_V1_VERSION'
// Support for Java 8 features coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'}
- Set
coreLibraryDesugaringEnabled
,sourceCompatibility
, andtargetCompatibility
to allow your application to make use of Java 8 features like Lambda expressions - Add Amplify Core and Desugaring libraries to the
dependencies
block
Run Gradle Sync
Android Studio requires you to sync your project with your new configuration. To do this, click Sync Now in the notification bar above the file editor.
When complete, you will see CONFIGURE SUCCESSFUL in the output in the Build tab at the bottom of your screen.
3. Provision the backend with Amplify CLI
To start provisioning resources in the backend, change directories to your project directory and run amplify init
:
amplify init
Enter the following when prompted:
? Enter a name for the project `MyAmplifyApp`? Initialize the project with the above configuration? `No`? Enter a name for the environment `dev`? Choose your default editor: `Android Studio`? Choose the type of app that you're building `android`? Where is your Res directory: `app/src/main/res`? Select the authentication method you want to use: `AWS profile`? Please choose the profile you want to use `default`
Upon successfully running amplify init
, you will see a configuration file created in ./app/src/main/res/raw/
called amplifyconfiguration.json
.
This file will be bundled into your application so that the Amplify libraries know how to reach your provisioned backend resources at runtime.
4. Initialize Amplify in the application
Create an Application
class and add the Amplify initialization into its onCreate()
to initialize Amplify once in your application.
Right-click on your namespace (e.g. com.example.MyAmplifyApp
), click New, and click Java Class or Kotlin File/Class depending on which language you choose.
Configure the new class in New Java Class:
- Enter MyAmplifyApp in the Name field
- Press enter
- Extend MyAmplifyApp from android.app.Application by adding
extends Application
to your class
Initialize Amplify by adding an onCreate
method with the following code:
public void onCreate() { super.onCreate();
try { Amplify.configure(getApplicationContext()); Log.i("MyAmplifyApp", "Initialized Amplify"); } catch (AmplifyException error) { Log.e("MyAmplifyApp", "Could not initialize Amplify", error); } }
Configure the new class in New Kotlin File/Class:
- Enter MyAmplifyApp in the Name field
- Press enter
- Extend MyAmplifyApp from android.app.Application by adding
: Application()
to your class
Initialize Amplify by adding an onCreate
method with the following code:
override fun onCreate() { super.onCreate()
try { Amplify.configure(applicationContext) Log.i("MyAmplifyApp", "Initialized Amplify") } catch (error: AmplifyException) { Log.e("MyAmplifyApp", "Could not initialize Amplify", error) }}
This overrides the onCreate()
to initialize Amplify when your application is launched.
Next, configure your application to use your new custom Application
class. Open manifests > AndroidManifest.xml, and add a android:name
attribute with the value of your new class name:
<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.MyAmplifyApp">
<!-- Add the android:name attribute to the application node --> <application android:name=".MyAmplifyApp" ... </application></manifest>
Next, build and run the application. In logcat, you'll see a log line indicating success:
com.example.MyAmplifyApp I/MyAmplifyApp: Initialized Amplify
Next steps
Congratulations! You've created a skeleton app and are ready to start adding Amplify categories to your application. The following are some categories that you can start to build into your application:
- Analytics - for logging metrics and understanding your users
- API (GraphQL) - for adding a GraphQL endpoint to your app
- API (REST) - for adding a REST endpoint to your app
- Authentication - for managing your users
- DataStore - for making it easier to program for a distributed data store for offline and online scenarios
- Geo - to use location data and map UI components.
- Predictions - to detect text, images, and more!
- Storage - store complex objects like pictures and videos to the cloud.
Further customization
Async Programming Model
Most functionalities in Amplify Android are exposed as asynchronous functions. These functions return immediately, returning a result at a later time:
val post = Post.builder() .title("My First Post") .build()
// Time 0: save() is called.Amplify.DataStore.save(post, { // Time 2, later: a result or error is emitted Log.i("MyAmplifyApp", "Saved a post") }, { // Time 2, later: a result of error is emitted Log.e("MyAmplifyApp", "Save failed", it) })// Time 1: save() yields execution to following lines,// but no result available, yet.
This is a familiar pattern to many Android developers, and so is the default way of interacting with Amplify Android.
However, this development model has some challenges. Let's say you need to wait for an operation to complete, so that you can perform additional logic that depends on its result. This can quickly become unmaintainable, resulting in a situation known as "callback hell":
Consider a relational model where the creation of a Post
also requires the creation of a User
for the editor, and a PostEditor
object to link the two together:
Post post = Post.builder() .title("My First Post") .build();
User editor = User.builder() .username("Nadia") .build();
PostEditor postEditor = PostEditor.builder() .post(post) .user(editor) .build();
Using callbacks, you can save these objects via:
Amplify.DataStore.save(post, { Log.i("MyAmplifyApp", "Post saved") Amplify.DataStore.save(editor, { Log.i("MyAmplifyApp", "Editor saved") Amplify.DataStore.save(postEditor, { Log.i("MyAmplifyApp", "PostEditor saved") }, { Log.e("MyAmplifyApp", "PostEditor not saved", it) } ) }, { Log.e("MyAmplifyApp", "Editor not saved", it) } ) }, { Log.e("MyAmplifyApp", "Post not saved", it) })
After three calls, you're no longer writing down the page, you're writing down-and-right. As your program grows, this may become difficult to scale.
There are a variety of different technologies that aim to solve this particular problem: Promises/Futures, RxJava, Kotlin Coroutines, and more.
Amplify Android includes optional support for Kotlin Coroutines and RxJava.
Kotlin Coroutines support
Amplify provides an optional and separate API surface which is entirely focused on using Kotlin's coroutines and flows.
To use it, import Amplify
facade from core-kotlin
instead of from core
. See the Installation notes below for more details.
With the Coroutines APIs, most Amplify functions are expressed as suspend
functions. Suspending functions can be launched using one of the lifecycle-aware coroutine scopes in the Android Architecture components:
import com.amplifyframework.kotlin.core.Amplify// ...
val post = Post.builder() .title("My First Post") .build()
lifecycleScope.launch { try { Amplify.DataStore.save(post) // This is suspending function! Log.i("AmplifyKotlinDemo", "Saved a post") } catch (failure: DataStoreException) { Log.e("AmplifyKotlinDemo", "Save failed", failure) }}
Coroutines can greatly improve the readability of dependent, asynchronous calls. Moreover, you can use scopes, dispatchers, and other Kotlin coroutine primitives to get more control over your execution context.
Let's consider what happens when you have three dependent operations. You want to save a Post
, then an Editor
, and finally a PostEditor
. With Amplify's coroutines interface, you can write these operations sequentially:
lifecycleScope.launch { try { listOf(post, editor, postEditor) .forEach { Amplify.DataStore.save(it) } Log.i("AmplifyKotlinDemo", "Post, Editor, and PostEditor saved") } catch (failure: DataStoreException) { Log.e("AmplifyKotlinDemo", "An item failed to save", failure) }}
In Amplify's vanilla APIs, this would have created a large block of code with three nested callbacks.
Install coroutine support
Amplify's coroutine support is included in an optional module, core-kotlin
.
-
Under Gradle Scripts, open build.gradle (Module :app), and add the following line in
dependencies
:dependencies {// Add the below line in `dependencies`implementation 'com.amplifyframework:core-kotlin:ANDROID_V1_KOTLIN_VERSION'} -
Wherever you use the
Amplify
facade, importcom.amplifyframework.kotlin.core.Amplify
instead ofcom.amplifyframework.core.Amplify
:import com.amplifyframework.kotlin.core.Amplify
Using Kotlin primitives
Amplify tries to map the behavior of your callback-based APIs to Kotlin primitives in an intuitive way. Functions whose callbacks emit a single value (or error) are now expressed as suspending functions, returning the value instead. Functions whose callbacks emit a stream of values will now return Kotlin Flow
s, instead.
Special cases with Kotlin
Some APIs return an operation which can be cancelled. Examples include realtime subscriptions to an API, and uploading/downloading objects from Storage.
API subscriptions with Kotlin
The API category's subscribe()
function uses both a suspend function and a Flow. The function suspends until the API subscription is established. Then, it starts emitting values over the Flow.
lifecycleScope.async { try { Amplify.API.subscribe(request) // Suspends until subscription established .catch { Log.e("AmplifyKotlinDemo", "Error on subscription", it) } .collect { Log.i("AmplifyKotlinDemo", "Data on subscription = $it") } } catch (error: ApiException) { Log.e("AmplifyKotlinDemo", "Failed to establish subscription", error) }}
Storage upload and download operations with Kotlin
The Storage category's downloadFile()
and uploadFile()
functions are bit more complex. These APIs allow you to observe transfer progress, and also to obtain a result. Progress results are delivered over a Flow, returned from the progress()
function. Completion events are delivered by a suspending result()
function.
// Downloadval download = Amplify.Storage.downloadFile(remoteKey, localFile)
lifecycleScope.async { download .progress() .collect { Log.i("AmplifyKotlinDemo", "Download progress = $it") }}
lifecycleScope.async { try { val result = download.result() Log.i("AmplifyKotlinDemo", "Download finished! ${result.file.path}") } catch (failure: StorageException) { Log.e("AmplifyKotlinDemo", "Download failed", failure) }}
// Uploadval upload = Amplify.Storage.uploadFile(remoteKey, localFile)
lifecycleScope.async { upload .progress() .collect { Log.i("AmplifyKotlinDemo", "Upload progress = $it") }}lifecycleScope.async { try { val result = upload.result() Log.i("AmplifyKotlinDemo", "Upload finished! ${result.key}") } catch (failure: StorageException) { Log.e("AmplifyKotlinDemo", "Upload failed", failure) }}
Using RxJava with Amplify
Amplify also provides a set of APIs that expose Reactive Extensions, a cross-platform library for asynchronous and event-based programs.
To use it, you'll interact with the RxAmplify
facade instead of the default Amplify
facade.
import com.amplifyframework.rx.RxAmplify;// ...
Post post = Post.builder() .title("My First Post") .build();
RxAmplify.DataStore.save(post) .subscribe( () -> Log.i("RxAmplifyDemo", "Saved a post"), failure -> Log.e("RxAmplifyDemo", "Save failed", failure) );
Compared to the traditional callback API, this doesn't make a big difference when used for a single method call.
However, it greatly improves readability when chaining asynchronous calls. Moreover, you can use standard RxJava operators to compose other complex functionality into readable chunks.
Let's revisit your nested example where you saved Post
, Editor
, and PostEditor
. With Amplify's RxJava interface you can merge these operations together.
Completable.mergeArray( RxAmplify.DataStore.save(post), RxAmplify.DataStore.save(editor)).andThen( RxAmplify.DataStore.save(postEditor)).subscribe( () -> Log.i("RxAmplifyDemo", "Post, Editor, and PostEditor saved"), failure -> Log.e("RxAmplifyDemo", "One or more items not saved", failure));
Compared to nesting these dependent calls in callbacks, this provides a much more readable pattern.
Install RxJava support
Amplify's RxJava support is included in an optional module, rxbindings
. To start using the Rx APIs, add the following dependency to your application's Gradle file:
Under Gradle Scripts, open build.gradle (Module :app).
Add the following line in dependencies
:
dependencies { // Add the below line in `dependencies` implementation 'com.amplifyframework:rxbindings:ANDROID_V1_VERSION'}
Using Rx primitives
Amplify tries to map the behavior of your callback-based APIs to well-known Rx primitives in an intuitive way. Functions whose callbacks emit a single value (or error) will now return Rx Single
s, instead. Functions whose callbacks emit no particular value will now return Rx Completable
s, instead. Lastly, functions whose callbacks emit a stream of values will now return Observable
s, instead.
Special cases with RxJava
Some APIs return an operation which can be cancelled. Examples include subscribing to an API or uploading or downloading objects from Storage.
API subscriptions with RxJava
The API category's subscribe()
method exposes two Observable
s: one for subscription data, and one for connection state. You can access these Observable
s using observeConnectionState()
and observeSubscriptionData()
on the returned operation:
RxSubscriptionOperation<? extends GraphQLResponse<?>> subscription = RxAmplify.API.subscribe(request);
subscription .observeConnectionState() .subscribe( connectionStateEvent -> Log.i("RxAmplifyDemo", String.valueOf(connectionStateEvent)) );
subscription .observeSubscriptionData() .subscribe( data -> Log.i("RxAmplifyDemo", "Data on subscription = " + data), failure -> Log.e("RxAmplifyDemo", "Subscription failed", failure), () -> Log.i("RxAmplifyDemo", "Subscription completed") );
Storage upload and download operations with RxJava
The Storage category's downloadFile()
and uploadFile()
work largely the same way. uploadFile()
and downloadFile()
both return an operation containing a Single
and an Observable
. The Single
can be used to obtain the result of the download, and the Observable
can be used to monitor download/upload progress.
// DownloadRxProgressAwareSingleOperation<StorageDownloadFileResult> download = RxAmplify.Storage.downloadFile(remoteKey, localFile);
download .observeProgress() .subscribe( progress -> Log.i("RxAmplifyDemo", "Download progress = " + progress.toString()) );
download .observeResult() .subscribe( result -> Log.i("RxAmplifyDemo", "Download finished! " + result.getFile().getPath()), failure -> Log.e("RxAmplifyDemo", "Download failed", failure) );
// UploadRxProgressAwareSingleOperation<StorageUploadFileResult> upload = RxAmplify.Storage.uploadFile(remoteKey, localFile);
upload .observeProgress() .subscribe( progress -> Log.i("RxAmplifyDemo", "Upload progress = " + progress.toString()) );
upload .observeResult() .subscribe( result -> Log.i("RxAmplifyDemo", "Upload finished! " + result.getKey()), failure -> Log.e("RxAmplifyDemo", "Upload failed", failure) );
Use existing AWS resources
An application’s backend is built with cloud resources such as AWS AppSync GraphQL APIs, Amazon S3 storage, and Amazon Cognito authentication. The Amplify CLI simplifies the provisioning of new backend resources across these different categories. However, you can alternatively use the Amplify libraries to add or re-use existing AWS resources that you provisioned without the CLI. The Amplify libraries support configuration through the amplifyconfiguration.json file which defines all the regions and service endpoints for your backend AWS resources.
Add an existing AWS resource to an Android application
Before you can add an existing AWS resource to an Android application, the application must have the Amplify libraries installed. For detailed instructions, see Install Amplify Libraries.
1. Manually create the Amplify configuration file for your Android project
First, locate your project’s res
folder. For example, if the name of your project is MyAmplifyApp, you can find the res
folder at the following location, MyAmplifyApp/app/src/main/res
:
Next, in your project’s res
folder, create a new folder named raw
.
Finally, in the raw
folder, create a file named amplifyconfiguration.json
. At this point the contents of your amplifyconfiguration.json
file can be an empty object, {}
.
2. Initialize Amplify in your application
To initialize Amplify when your application is launched, you will need to create a new Application
class and override its onCreate()
method.
First, locate your application’s namespace where you will create the new application class. For example, if your application is named MyAmplifyApp, navigate to either MyAmplifyApp/app/src/main/java/com.example.MyAmplifyApp
or MyAmplifyApp/app/src/main/kotlin/com.example.MyAmplifyApp
depending on the programming language you are using.
From the Android Studio main menu, choose File -> New and select either Java Class or Kotlin File/Class depending your programming language.
Select Class, and specify a name for your new class in the Name field.
Paste the following code for the onCreate()
method inside your new class:
public void onCreate() { super.onCreate();
try { Amplify.configure(getApplicationContext()); Log.i("MyAmplifyApp", "Initialized Amplify"); } catch (AmplifyException e) { Log.e("MyAmplifyApp", "Could not initialize Amplify", e); }}
override fun onCreate() { super.onCreate()
try { Amplify.configure(applicationContext) Log.i("MyAmplifyApp", "Initialized Amplify") } catch (error: AmplifyException) { Log.e("MyAmplifyApp", "Could not initialize Amplify", error) }}
Next, configure your application to use your new custom Application class
. Open the AndroidManifest.xml
file located in your project directory at app/src/main/AndroidManifest.xml
.
Add the android:name
attribute to the application node. For example, if the application name is MyAmplifyApp and the new class is named MyAmplifyApplication, the update to the AndroidManifest.xml
file looks as follows:
<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.MyAmplifyApp">
<!-- Add the android:name attribute to the application node --> <application android:name=".MyAmplifyApplication" ... </application></manifest>
3. Edit your configuration file to use an existing AWS resource
Now you’re ready to customize your application’s amplifyconfiguration.json
file to specify an existing AWS resource to use.
Note that before you can add an AWS resource to your application, the application must have the Amplify libraries installed. If you need to perform this step, see Install Amplify Libraries.
Select a category from the following list to view an example amplifyconfiguration.json
file you can use as a template to author your own amplifyconfiguration.json
file:
- See the Analytics category to use existing AWS Pinpoint resources.
- See the API (GraphQL) category to use existing AWS AppSync resources.
- See the API (REST) category to use existing Amazon API Gateway and AWS Lambda resources.
- See the Authentication category to use existing Amazon Cognito resources.
- See the Storage category to use existing Amazon S3 resources.