Overview
Hey there, welcome! Let’s begin with a question : How important is data for your IT product? The answer surely is very, isn’t it? I cannot imagine any product that can contradict this statement. Infact, it is so vital that developers take utmost care for its management and storage. If handled carelessly, it can lead to poor performance and complex product scalability which is nothing less than a nightmare for any company! This is the main reason product companies invest a ton of money to make sure their servers are faster, scalable and provide low latency. But as we all know, every rose has its thorn. To purchase such efficient servers, one has to break the bank 😀
Serverless computing to the rescue!
Server cost is one of many situations where a serverless ecosystem shines. In this system, the developers do not have to worry about managing and scaling the servers. All the tedious work is managed by a service-provider like AWS which has servers scattered all over the planet to provide low latency and strong networking.
DynamoDB – What’s that?
DynamoDB, a database management product of AWS, is based on such technology. It uses NoSQL database. Clients need to follow demand-based payment instead of purchasing costly, standalone servers.
When should I use it?
DynamoDB has a bundle of advantages if you are looking for a reliable, secure data storage mechanism.
- Data auto-backup
- Security with encryption
- Quick response at any scale
- Flexibility
These are the reasons billion-dollar companies like Samsung, Lyft and Netflix rely on it. However, it doesn’t provide a real-time environment. Also, it doesn’t allow us to add server-side scripting like Firebase.
This article focuses on connecting DynamoDB with an Android application. I, being a Kotlin-fanatic, will also try to attach it with the powerful wings of Kotlin!! 😉
Enough with the theory! Let’s be practical.
Setting up AWS
I’ll skip the regular AWS registration steps and will jump directly to the core.
It is a very simple process which can be divided into following steps:
- Open DynamoDB console and create a table
- Add an item into the table (optional)
- Create an Identity Pool using Amazon Cognito
Cognito is amazon’s service to provide authentication and accessibility to users of our apps to engage with their services. There are 2 types of pools :- User Pool – To allow users sign in to our app using Amazon Cognito
- Identity Pool – To provide temporary AWS credentials to users for accessing AWS services such as DynamoDB
Identity Pool fits the needs of our use-case.
Don’t forget to tick the checkbox for Unauthenticated Identities because the users of our app will access the database without any authentication. - Assigning the required roles to the pool
We need to create a role for the pool to limit the access of the apps using it. We only need to assign DynamoDB access for our case.
Here, I’ve added a policy to provide full access to DynamoDB but you can restrict it as per your needs.You’ll be provided with Identity Pool ID and a Region which are to be used in the app to establish the connection with the database.
That’s pretty much it for backend configurations. Now, let’s fire up the Android Studio!
Android App Overview
DynamoDB operations are asynchronous. This is the reason I chose to tie it up with Kotlin’s abilities. Kotlin provides coroutiones which brought an evolution in async-programming. They are basically OS-independent, light-weight threads which can execute suspended functions and at the same time they provide a great support for easy thread-switching which is a “must-have” feature for async-programming. We’ll also use Kotlin’s structured concurrency to help us with the process cancelation.
We’ll create a cricket team app to display a list of players stored on DynamoDB with create, update and delete features (CRUD operations). Talking more about the app structure, we’ll use the MVVM architecture pattern with LiveData and Android’s data binding to make the most out of Android.
Here’s what we are going to develop :
Setting up Android App
Omitting the usual app creation, UI designing process and wiring up the structure, I’ll concentrate on Kotlin and DynamoDB.
- Add dependencies
First things first. We need to add the following dependencies in app-level gradle file to use DynamoDB & Kotlin’s features.//coroutines, ktx & scopes implementation 'androidx.core:core-ktx:1.1.0' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3' implementation 'androidx.activity:activity-ktx:1.1.0' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0' //aws implementation 'com.amazonaws:aws-android-sdk-core:2.9.2' implementation 'com.amazonaws:aws-android-sdk-ddb:2.9.2' implementation 'com.amazonaws:aws-android-sdk-ddb-document:2.4.4'
- Add DynamoDB credentials
We will create an object wherein we’ll define identity pool ID, allotted region and table name which will be used while communicating with AWS.object DynamoDBHelper { const val COGNITO_IDP_ID = "<your-identity-pool-id>" val COGNITO_IDP_REGION = Regions.US_EAST_2 //allotted region const val TABLE_NAME = "Players" //your table name }
- Establish the connection
In order to access the DynamoDB Table which we created in the console, we’ll need AmazonDynamoDBClient object which can be constructed using CognitoCachingCredentialsProvider as follows:private val credentialsProvider = CognitoCachingCredentialsProvider( application.applicationContext, DynamoDBHelper.COGNITO_IDP_ID, DynamoDBHelper.COGNITO_IDP_REGION ) private val dbClient = AmazonDynamoDBClient(credentialsProvider).apply { /** * Don't forget to mention the region for database client. If not, it defaults to US_EAST_1 */ setRegion(Region.getRegion(DynamoDBHelper.COGNITO_IDP_REGION)) } private lateinit var table: Table private suspend fun getTable(): Table { if (::table.isInitialized) return table return suspendCoroutine { continuation -> table = Table.loadTable(dbClient, DynamoDBHelper.TABLE_NAME) continuation.resume(table) } }
We need to use the credentials in this process as coded in the above snippet. We
also need to set the region for AmazonDynamoDBClient externally otherwise it defaults to US_EAST_1.
Loading a table is an asynchronous process. Hence, I have used Kotlin’s suspendCoroutine to suspend it and make it behave as a synchronous call. - Create an execution environment
In order to execute database operation on a background thread, I’ve created a common method using IO dispatcher of coroutines which is handled in a viewModelScope.private fun execute(executionBlock: suspend CoroutineScope.() -> Unit) { viewModelScope.launch(Dispatchers.IO) { try { state.postValue(ListScreenState.Loading) executionBlock.invoke(this) /** * delay is added because postValue is synchronized. (if not, posting idle state right after posting execution value will create issues) */ delay(500) state.postValue(ListScreenState.Idle) } catch (e: Exception) { state.postValue(ListScreenState.Error) } } }
We’re all set for implementing the core business logic of the app.
Business logic
The app consists of data read, create, delete and update features. Let’s look at each of these in detail.
- Read
We need to fetch/read the list of players from the database. We’ll use the scan method of the table to get the data. We need to pass the ScanOperationConfig configuration object as the parameter.internal fun getAllPlayers() { execute { val players = arrayListOf<PlayerDataModel>() getTable().scan( ScanOperationConfig() ).allResults.forEach { players.add(Gson().fromJson(Document.toJson(it), PlayerDataModel::class.java)) } state.postValue(ListScreenState.PlayerListFetched(players)) } }
After fetching the results, I’m converting each retrieved document into its corresponding data model using Gson.
- Create
To insert an item into the table, we need to map the data into a Document and then pass it into the putItem method of the table. [Here, I’ve used timestamp in milliseconds as primitive key of the item]internal fun createPlayer(player: PlayerDataModel) { execute { getTable().putItem(Document.fromJson(Gson().toJson(player))) getAllPlayers() } }
- Delete
In order to delete an item, we can use item ID/primitive key which is to be passed in the deleteItem method.internal fun deletePlayer(pos: Int, id: String) { execute { getTable().deleteItem(Primitive(id)) state.postValue(ListScreenState.PlayerDeleted(pos)) } }
- Update
Updating an item is a little tricky. We need to set the older value of the document as originalValue and new data as currentValue. The SDK compares these values and updates the parameters accordingly.internal fun updatePlayer(player: PlayerDataModel) { execute { getTable().updateItem( getTable().getItem(Primitive(player.id)).apply { putAll(Document.fromJson(Gson().toJson(player))) }, Primitive(player.id), UpdateItemOperationConfig().apply { returnValue = ReturnValue.ALL_NEW } ) getAllPlayers() } }
Here, we need to pass the UpdateItemOperationConfig object with the required returnValue. After the operation, it’ll return the data based on the returnValue parameter.
That’s it! We are done with the basic DynamoDB app.
There are a ton of data management methods provided by DynamoDB for custom queries and other complex operations. As I don’t want to stretch the length of this articIe anymore, I’ll keep those for you to explore.
TL;DR
DynamoDB is one of the leading NoSQL-powered databases based on serveless technology. Being an AWS-product, it offers premium features such as speed, data security and auto-data backup. It can be easily integrated into apps and websites using its easy-to-use SDKs. Due to its reliability, many huge tech-giants are its prominent users.
On Android, Kotlin can empower its integration using its concurrency features which include coroutines, suspended functions and scopes.
Hope you enjoyed the article and grabbed something useful out of it. Have a great, techy day ahead! 🙂