概要
DynamoDB Enhanced ClientでDynamoDBに接続する簡易ログインアプリを作成しました。
プロジェクト全体のソースコードはこちらに置いてあります。
https://github.com/ist-h-i/DynamoDbEnhancedClient
AWSの設定
DynamoDB
テーブルを作成
アクセスキーID、シークレットアクセスキーを取得
AWSコンソール > IAM
アクセスキーID、シークレットアクセスキーを控えるか.csvファイルをダウンロード
シークレットキーはキー作成時しか参照できないため注意
アプリ実装
MainActivity.kt
package com.example.enhancedclient
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.example.enhancedclient.api.DynamoApi
import com.example.enhancedclient.databinding.ActivityMainBinding
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// レイアウト紐付け
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// ログインボタン押下時の設定
binding.loginButton.setOnClickListener {
val userId: String = binding.userId.text.toString()
val password: String = binding.password.text.toString()
// 未入力欄がある場合
if (userId.isBlank() || password.isBlank()) {
showLongToast(R.string.inputUserInfo)
return@setOnClickListener
}
// 外部通信のためスレッド生成
val queryDynamoDb = Thread {
try {
val dynamoApi = DynamoApi()
// Query結果
val userRecord = dynamoApi.getDoesExistUser(userId, password)
if (userRecord) {
// User情報が登録済みの場合
showLongToast(R.string.loginSuccess)
// ログイン後後続処理はこの下に記載
} else {
// User情報がない場合
showLongToast(R.string.loginFailed)
}
} catch (error: DynamoDbException) {
showLongToast(R.string.unknownError)
}
}
queryDynamoDb.start()
try {
queryDynamoDb.join()
} catch (error: InterruptedException) {
Toast.makeText(this, R.string.unknownError, Toast.LENGTH_LONG).show()
}
}
}
private fun showLongToast(messageInt: Int) {
// 別スレッドからUIを操作
val handler = Handler(Looper.getMainLooper())
handler.post {
Toast.makeText(this, messageInt, Toast.LENGTH_LONG).show()
}
}
}
DynamoApi
DynamoDBと接続してデータを読み込むクラス
package com.example.enhancedclient.api
import android.app.Activity
import com.example.enhancedclient.BuildConfig
import com.example.enhancedclient.model.User
import com.example.enhancedclient.model.UserSubscriber
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbAsyncTable
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedAsyncClient
import software.amazon.awssdk.enhanced.dynamodb.Key
import software.amazon.awssdk.enhanced.dynamodb.TableSchema
import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags
import software.amazon.awssdk.enhanced.dynamodb.model.PagePublisher
import software.amazon.awssdk.enhanced.dynamodb.model.QueryConditional
import software.amazon.awssdk.enhanced.dynamodb.model.QueryEnhancedRequest
import software.amazon.awssdk.regions.Region
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient
class DynamoApi : Activity() {
// スキーマ作成
private val userSchema: TableSchema<User> =
TableSchema.builder(User::class.java).newItemSupplier { User() }
.addAttribute(String::class.java) { a ->
a.name("GroupName").getter(User::userId).setter { user, s -> user.userId = s }
.tags(StaticAttributeTags.primaryPartitionKey())
}.addAttribute(String::class.java) { a ->
a.name("CognitoIdentityId").getter(User::password)
.setter { user, s -> user.password = s }
.tags(StaticAttributeTags.primarySortKey())
}.build()
// クライアント作成
private var enhancedClient: DynamoDbEnhancedAsyncClient =
DynamoDbEnhancedAsyncClient.builder().dynamoDbClient(
DynamoDbAsyncClient.builder().region(
Region.AP_NORTHEAST_1
).credentialsProvider(
StaticCredentialsProvider.create(
AwsBasicCredentials.create(BuildConfig.accessKeyId, BuildConfig.secretAccessKey)
)
).build()
).build()
//テーブルインスタンス作成
private var userTable: DynamoDbAsyncTable<User> =
enhancedClient.table("LoginAppUser", userSchema)
// 条件に合うユーザーが登録されているか確認
fun getDoesExistUser(userId: String, pass: String): Boolean {
val keyEqual = QueryConditional.keyEqualTo { b: Key.Builder ->
b.partitionValue(userId).sortValue(pass)
}
val tableQuery = QueryEnhancedRequest.builder().queryConditional(keyEqual).limit(1).build()
val pagePublisher: PagePublisher<User> = userTable.query(tableQuery)
val subscriber = UserSubscriber()
pagePublisher.subscribe(subscriber)
val userList: MutableList<User> = ArrayList()
pagePublisher.items().subscribe { e: User -> userList.add(e) }.exceptionally { null }.join()
return userList.isNotEmpty()
}
}
User
Modelクラス
package com.example.enhancedclient.model
class User {
var userId: String? = null
var password: String? = null
constructor(
userId: String?,
password: String?,
) {
this.userId = userId
this.password = password
}
constructor()
}
UserSubscriber
読み込んだ各ページからアイテムを収集するクラス
package com.example.enhancedclient.model
import org.reactivestreams.Subscriber
import org.reactivestreams.Subscription
import software.amazon.awssdk.enhanced.dynamodb.model.Page
import java.util.Objects
import java.util.concurrent.CountDownLatch
import java.util.stream.Collectors
class UserSubscriber : Subscriber<Page<User>> {
// 読み込み完了まで待機するためのLatch
private val latch = CountDownLatch(1)
private val itemsFromAllPages: List<User> = ArrayList()
private var subscription: Subscription? = null
override fun onSubscribe(sub: Subscription?) {
subscription = sub
subscription!!.request(1L)
try {
// 待機開始
latch.await()
} catch (e: InterruptedException) {
throw RuntimeException(e)
}
}
override fun onNext(userPage: Page<User>) {
userPage.items()?.let {
itemsFromAllPages.stream().filter { obj: User? -> Objects.nonNull(obj) }
.collect(Collectors.toList<Any>()).addAll(it)
}
subscription!!.request(1L)
}
override fun onError(throwable: Throwable?) {}
override fun onComplete() {
// 待機完了
latch.countDown()
}
fun getSubscribedItems(): List<User> {
return itemsFromAllPages
}
}
gradle.properties
アクセスキーID、シークレットアクセスキーを追加
accessKeyId=xxxxxxxxxxxxxxxxxxxx
secretAccessKey=xxxxxx/xxxxxxxxxxxxxxxxxxxxxxxxxxxx+xxxx
build.gradle.kts
defaultConfigに設定値を追加
android {
defaultConfig {
buildConfigField("String", "accessKeyId", "\"${project.properties["accessKeyId"]}\"")
buildConfigField("String", "secretAccessKey", "\"${project.properties["secretAccessKey"]}\"")
}
}
drawableのリソース等は以下参照
まとめ
DynamoDB Enhanced Clientを使ってDynamoDBにアクセスしました。標準のDynamoDB Clientと比較しテーブル作成やデータ操作を簡潔かつ直感的にコーディングできるため使いやすい印象でした。
また既存のモデルクラスを変更することなくスキーマ作成できることも大きなメリットであると感じました。
標準のDynamoDB Clientと比較してみてください。
KotlinでDynamoDBに接続 - その1
https://qiita.com/ist-h-i/items/c77b2858d87af4522e0a