LoginSignup
3
0

KotlinでDynamoDBに接続 - その2

Posted at

概要

DynamoDB Enhanced ClientでDynamoDBに接続する簡易ログインアプリを作成しました。

プロジェクト全体のソースコードはこちらに置いてあります。
https://github.com/ist-h-i/DynamoDbEnhancedClient

AWSの設定

DynamoDB

テーブルを作成

アクセスキーID、シークレットアクセスキーを取得
AWSコンソール > IAM
IAM.png

ユーザーを作成
IAM User.png
Create User.png

セキュリティ認証情報からアクセスキーを作成
Create Key.png
External App.png

アクセスキーID、シークレットアクセスキーを控えるか.csvファイルをダウンロード
Key Info.png

シークレットキーはキー作成時しか参照できないため注意

アプリ実装

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

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0