Front-End Web & Mobile

User settings sync for Android with AWS AppSync

Amazon Cognito Sync is a service that you can use for syncing application-related user profile data across devices. The client library caches the data locally so that the app can read and write data, regardless of the device connectivity state. The data stored is generally shown as a key-value pair, where the keys and values are both strings.

However, many organizations need to store data in a common store. For example, you might want to do backend searches to enable you to segment users, do A/B testing, store binary data like profile pictures, or link the data to alternate data sources.

In a previous article, I created a data store for storing user settings by using AWS AppSync, which provides a similar structure as Amazon Cognito Sync, but has some additional functionality. AWS AppSync is a GraphQL-based managed service with real-time and offline capabilities.

In this article, I build a simple Android application that demonstrates the ability to synchronize application-related user profile data across devices by using the data store that I created earlier. If you haven’t already done so, return to the previous article and set up the cloud data store.

Option 1: Quick start (15 minutes)

In this quick start option, I take an existing sample project that you can download, update it to specify the location of the user settings store in the AWS Cloud, run the sample app, and then verify that the data is stored in the appropriate place.

  1. Download the sample repository. Choose Clone or download, and then choose Download ZIP.
  2. Unpack the ZIP file.
  3. Open the project in Android Studio.
  4. Download the AppSync.json file from the AWS AppSync console:
    • Open the AWS AppSync console.
    • Choose the GraphQL API to integrate.
    • Scroll to the bottom of the first page, and then choose Download under Step 2.
    • Rename the downloaded file appsync.json (all characters must be lowercase), and then place the file in the res/raw folder of the project.
  5. Download the awsconfiguration.json file, and add it to your project:
    • Open the AWS Mobile Hub console, and then choose your project.
    • Choose the Integrate button on the iOS app panel.
    • Choose Download Cloud Config. This downloads the awsconfiguration.json file.
    • Copy the awsconfiguration.json file to the res/raw folder of the project.
  6. Compile and run your project on an Android emulator.

The sample mobile app provides four theme options. Changing the theme also changes the color of the banner. You can change the theme by choosing the menu button, and then choose Preferences. If you check the backend data store, you’ll see that your theme is stored in the settings store. Also, if you implement the iOS version of the app, you’ll notice that changes to the theme on Android are also reflected in the iOS app.

Option 2: Access the settings store from your own app

When you integrate user settings within your own app, I recommend using Android Architecture Components to organize the code. To organize your work:

  • Configure your project for AWS AppSync.
  • Download the GraphQL schema and create an operations file.
  • Run a build to generate the required code artifacts.
  • Add the appsync.json and awsconfiguration.json files to your project.
  • Create a repository that provides access to the settings.

Step 1: Configure your project for AWS AppSync

Edit the project-level build.gradle. Add the following in the buildscript section:

buildscript {
    ext.kotlin_version = '1.2.40'
    ext.appsync_version = '2.6.17'
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.2.0-alpha12'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath "com.amazonaws:aws-android-sdk-appsync-gradle-plugin:$appsync_version"

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

It’s important to include the lines with “appsync” in them. This includes the ext.appsync_version and the classpath. The sample project is written in Kotlin, but that isn’t a requirement.  The process works just as well if you use Java for developing Android applications.

Edit the app-level build.gradle. Add the AWS AppSync plugin to the top of the file. This must come before the Kotlin plugins if you’re using Kotlin:

apply plugin: 'com.android.application'
apply plugin: 'com.amazonaws.appsync'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'

Add the AWS AppSync library to your dependencies:

dependencies {
  // other dependencies

    // AWS Mobile SDK
    implementation("com.amazonaws:aws-android-sdk-mobile-client:$aws_sdk_version@aar") { transitive = true }
    implementation("com.amazonaws:aws-android-sdk-auth-userpools:$aws_sdk_version@aar") { transitive = true }
    implementation("com.amazonaws:aws-android-sdk-auth-ui:$aws_sdk_version@aar") { transitive = true }
    implementation("com.amazonaws:aws-android-sdk-pinpoint:$aws_sdk_version")
    implementation("com.amazonaws:aws-android-sdk-appsync:$appsync_version")
}

Step 2: Create the GraphQL schema and operations files

Create a new folder in your Android project for the artifacts. In the left-hand sidebar, switch to the Project view. Expand app/src/main/. Right-click main, and choose New > Directory. Type graphql as the name, then choose OK. (Note that this directory MUST be in this precise location and named graphql.)

Download the AWS AppSync schema:

  • Open the AWS AppSync console, and choose your GraphQL API.
  • At the bottom of the API page, choose the Android tab.
  • Under step 3, choose schema.json from the Export schema drop-down.
    • Do NOT download the schema.graphql file because it’s not required for the code generation process that follows.
  • Copy the schema.json file into the graphql folder you just created.

Create an settings.graphql file:

  • Right-click graphql and choose New > File.
  • Type settings.graphql as the name, and then choose OK.
  • Copy the following code into the newly created file:
query GetSettings {
    getSettings {
        theme
        displayName
    }
}

mutation StoreSettings($settings: SettingsInput!) {
    storeSettings(settings: $settings) {
        theme
        displayName
    }
}

Step 3: Run a build for code generation

After the settings.graphql file is saved, build the project using Build > Make project. Ensure that the code generation worked by looking in app/build/generated/source/appsync. You should see two classes (GetSettingsQuery and StoreSettingsMutation).

Step 4: Download the AppSync.json and awsconfiguration.json files

These files describe the endpoints that are used by the mobile app:

  1. Download the AppSync.json file from the AWS AppSync console:
    • Open the AWS AppSync console.
    • Choose the GraphQL API to integrate.
    • Scroll to the bottom of the first page, and then choose Download under Step 2.
    • Rename the downloaded file appsync.json (all characters must be lowercase), and then place the file in the res/raw folder of the project.
  2. Download the awsconfiguration.json file, and add it to your project:
    • Open the AWS Mobile Hub console, and then choose your project.
    • On the Android app panel, choose the Integrate button.
    • Choose Download Cloud Config. This downloads the awsconfiguration.json file.
    • Copy the awsconfiguration.json file to the res/raw folder of the project.

Step 5: Create a repository

The repository pattern is used within Android Architecture Components as a repository of data. First, create an interface to represent the repository. This enables easier integration with dependency injection systems:

import android.arch.lifecycle.LiveData
import com.amazonaws.mobile.samples.appsyncsettings.models.Theme

/**
 * Description of the preferences repository.  Right now, we only have one preference -
 * the theme.
 */
interface PreferencesRepository {
    val theme: LiveData<String>

    fun updateTheme(theme: String)
}

This version is in Kotlin, but a similar concept exists for Java. Next, write a concrete implementation.  Again, I’m using Kotlin, but Java can provide the same functionality:

class AWSPreferencesRepository(applicationContext: Context) : PreferencesRepository {
    companion object {
        private val TAG = this::class.java.simpleName
    }

    private val mutableTheme: MutableLiveData<String> = MutableLiveData()
    private val appSyncClient: AWSAppSyncClient
    private val appSyncConfig = AppSync(applicationContext.readResourceFile(R.raw.appsync))

    override val theme: LiveData<String>
        get() = mutableTheme

    init {
        mutableTheme.value = “Default”

        // Get a connection to the AppSync client
        appSyncClient = AWSAppSyncClient.builder()
                .context(applicationContext)
                .credentialsProvider(AWSMobileClient.getInstance().credentialsProvider)
                .region(appSyncConfig.region)
                .serverUrl(appSyncConfig.graphqlEndpoint)
                .build()

        // Query the settings
        appSyncClient
                .query(GetSettingsQuery.builder().build())
                .responseFetcher(AppSyncResponseFetchers.CACHE_AND_NETWORK)
                .enqueue(object : GraphQLCall.Callback<GetSettingsQuery.Data>() {
                    override fun onResponse(response: Response<GetSettingsQuery.Data>) {
                        response.data()?.settings?.theme()?.let {
                            runOnUiThread { mutableTheme.value = it }
                        }
                    }

                    override fun onFailure(exception: ApolloException) {
                        Log.e(TAG, "Failed to execute GetSettings: ${exception.message}")
                    }
                })
    }

    override fun updateTheme(theme: String) {
        // Store the settings in AWS AppSync
        val settingsInput = SettingsInput.builder()
                .theme(theme)
                .displayName(theme)
                .build()
        val storeSettingsMutation = StoreSettingsMutation.builder()
                .settings(settingsInput)
                .build()
        appSyncClient.mutate(storeSettingsMutation).enqueue(object : GraphQLCall.Callback<StoreSettingsMutation.Data>() {
            override fun onResponse(response: Response<StoreSettingsMutation.Data>) {
                response.data()?.storeSettings()?.theme()?.let {
                    runOnUiThread { mutableTheme.value = it }
                }
            }

            override fun onFailure(exception: ApolloException) {
                Log.e(TAG, "Failed to execute StoreSettings: ${exception.message}")
            }
        })
    }
}

The AWS AppSync configuration is read from the appsync.json file into a model that represents the file. For Kotlin, implement an extension method to read the JSON file from the resources:

import android.content.Context

fun Context.readResourceFile(resourceId: Int)
        = resources.openRawResource(resourceId).bufferedReader().use { it.readText() } 

Then create the AWS AppSync model:

import com.amazonaws.regions.Regions
import org.json.JSONObject

enum class AppSyncAuthType {
    API_KEY,
    AWS_IAM,
    AMAZON_COGNITO_USER_POOLS
}

class AppSync(val jsonObject: JSONObject) {
    var graphqlEndpoint: String = ""
    var authType: AppSyncAuthType = AppSyncAuthType.API_KEY
    var region: Regions = Regions.DEFAULT_REGION
    var apiKey: String = ""

    constructor(json: String) : this(JSONObject(json))

    init {
        graphqlEndpoint = jsonObject.getString("graphqlEndpoint")
        region = Regions.fromName(jsonObject.getString("region"))
        apiKey = jsonObject.getString("apiKey")
        authType = AppSyncAuthType.valueOf(jsonObject.getString("authenticationType"))
    }
}

You can now use this repository in your own view model or an asynchronous thread for dealing with the user settings.

Conclusion

AWS AppSync provides many benefits over Amazon Cognito Sync for application settings, and we’ve just scratched the surface here. In AWS AppSync, you can store basic key-value pairs, complex objects, or even users’ profile pictures—and this data will be synchronized to all user devices. In addition, AWS AppSync offers a single data-access layer for application data with real-time updates and offline capabilities. This makes it an ideal tool for cloud-enabled data-driven apps. Learn more about AWS AppSync now.