Page updated Nov 8, 2023

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:

1import com.amplifyframework.kotlin.core.Amplify
2// ...
3
4val post = Post.builder()
5 .title("My First Post")
6 .build()
7
8lifecycleScope.launch {
9 try {
10 Amplify.DataStore.save(post) // This is suspending function!
11 Log.i("AmplifyKotlinDemo", "Saved a post")
12 } catch (failure: DataStoreException) {
13 Log.e("AmplifyKotlinDemo", "Save failed", failure)
14 }
15}

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:

1lifecycleScope.launch {
2 try {
3 listOf(post, editor, postEditor)
4 .forEach { Amplify.DataStore.save(it) }
5 Log.i("AmplifyKotlinDemo", "Post, Editor, and PostEditor saved")
6 } catch (failure: DataStoreException) {
7 Log.e("AmplifyKotlinDemo", "An item failed to save", failure)
8 }
9}

In Amplify's vanilla APIs, this would have created a large block of code with three nested callbacks.

Installation

Amplify's coroutine support is included in an optional module, core-kotlin.

  1. Under Gradle Scripts, open build.gradle (Module :app), and add the following line in dependencies:

    1dependencies {
    2 // Add the below line in `dependencies`
    3 implementation 'com.amplifyframework:core-kotlin:ANDROID_VERSION'
    4}
  2. Wherever you use the Amplify facade, import com.amplifyframework.kotlin.core.Amplify instead of com.amplifyframework.core.Amplify:

    1import com.amplifyframework.kotlin.core.Amplify

Usage

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 Flows, instead.

Special cases

Some APIs return an operation which can be cancelled. Examples include realtime subscriptions to an API, and uploading/downloading objects from Storage.

API subscriptions

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.

1lifecycleScope.async {
2 try {
3 Amplify.API.subscribe(request) // Suspends until subscription established
4 .catch { Log.e("AmplifyKotlinDemo", "Error on subscription", it) }
5 .collect { Log.i("AmplifyKotlinDemo", "Data on subscription = $it") }
6 } catch (error: ApiException) {
7 Log.e("AmplifyKotlinDemo", "Failed to establish subscription", error)
8 }
9}

Storage upload & download operations

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.

1// Download
2val download = Amplify.Storage.downloadFile(remoteKey, localFile)
3
4lifecycleScope.async {
5 download
6 .progress()
7 .collect { Log.i("AmplifyKotlinDemo", "Download progress = $it") }
8}
9
10lifecycleScope.async {
11 try {
12 val result = download.result()
13 Log.i("AmplifyKotlinDemo", "Download finished! ${result.file.path}")
14 } catch (failure: StorageException) {
15 Log.e("AmplifyKotlinDemo", "Download failed", failure)
16 }
17}
18
19// Upload
20val upload = Amplify.Storage.uploadFile(remoteKey, localFile)
21
22lifecycleScope.async {
23 upload
24 .progress()
25 .collect { Log.i("AmplifyKotlinDemo", "Upload progress = $it") }
26}
27lifecycleScope.async {
28 try {
29 val result = upload.result()
30 Log.i("AmplifyKotlinDemo", "Upload finished! ${result.key}")
31 } catch (failure: StorageException) {
32 Log.e("AmplifyKotlinDemo", "Upload failed", failure)
33 }
34}