こんにちは!
キャスレーコンサルティング CSVIT事業部 IT部の清水(皓)です。

今回は、IoTデバイスのスマートロックを利用した社内用スマホアプリの開発で使った、「Kotlin」と「Google Sheets API v4」について紹介します。

Google Sign-in API(余談)

最初から余談ですが、Google Sign-in APIについてです。

開発するスマホアプリは社員のみ利用することを想定しているため、社員に割り振られる社内用アカウントでスマホアプリにログインしてもらおうと考えました。

ただ、ちょっとしたスマホアプリなのにバックエンドにDBサーバを立てて、バックエンドでログイン処理をするようなことはしたくありませんでした。

弊社はG Suiteを使っているため、Google Sign-inを利用できれば楽ができると踏んで、Google Sign-in APIを使うことにしました。

Google Sign-in APIの使い方は、多くの方が紹介してくださっていますので、そちらを参照していただければと思います。

私も参考にさせていただきました。ありがとうございます。
Google Sign-In For Android

Qiita GoogleSignIn実装

Google Sheet API(本題)

ここからが本題です。

基本的にやりたいことを実装した後に、社内セキュリティの関係で利用ユーザのログを出す仕様になりました。
スマホアプリにログインした社員のアカウント名とログイン時間をログに出力し、社内で確認できるようにする必要があります。

ログイン処理同様に、バックエンドで処理をさせるほどの規模があるアプリではないため、もっと簡易な仕組みにしたいと思いました。

そこで、SpreadSheetにログを書き出して、細かい整形やらはGoogle Apps Scriptに任せれば良いと考え、Google Sheets APIを使うことにしました。
Google Apps Scriptの処理はここでは割愛させていただきます。

Google Sheets APIの使い方については以下を参考にしましたが、Kotlinによる実装に関する情報はなかなか得られませんでした。

そのため、今回初めてKotlinを触る私は書き方や使うメソッドに渡す引数が何かなど、結構悩みました。
Google Sheets
Google Sheets APIの紹介


セル値の読み取りと書き込み

スプレッドシートのアップデート

Google Sheets APIの有効化

Google Sign-In APIを実装する際に、Google Developer Consoleにプロジェクトを登録済みかと思います。

登録したプロジェクトでGoogle Sheets APIを有効化します。
Google Developer Consoleに登録していないプロジェクトでは有効化できませんし、プロジェクトに紐づいたアプリケーションでなければ実装できません。

ポチっと

ポチっと

これで有効になりました。

appレベルGradleにGoogle Sheets APIを設定

今回の実装時点で最新版のGoogle Sheets APIとのことでしたので、Google Sheets API v4を使用します。

「新しい」という響きに弱いです。

dependencies {
    // ・・・

    // Android用GoogleAPIクライアント
    implementation('com.google.api-client:google-api-client-android:1.28.0') {
        exclude group: 'org.apache.httpcomponents'
        exclude module: 'guava-jdk5'
    }
    // Google Sheets API
    implementation('com.google.apis:google-api-services-sheets:v4-rev571-1.25.0') {
        exclude group: 'org.apache.httpcomponents'
        exclude module: 'guava-jdk5'
    }
}
apply plugin: 'com.google.gms.google-services'

SpreadSheetに書き込む処理を実装

DriveにあるSpreadSheetへ書き込みを行う処理を実装します。
SpreadSheetは、アプリでサインインしたGoogleAccountで読み書き可能にしています。

    private fun writeLoginInfo() {
        // サービスのスコープとしてSpreadSheetsのRead/Writeを設定
        val scopes = listOf(SheetsScopes.SPREADSHEETS)
        // GoogleAccountCredentialを生成
        val credential = GoogleAccountCredential.usingOAuth2(applicationContext, scopes)
        // Google Sign-in API実装にて取得したGoogleAccount情報を利用。(社員以外は利用できないように)
        credential.selectedAccount = mACCOUNT!!.account
        // サービス呼び出し
        val jsonFactory = JacksonFactory.getDefaultInstance()
        val httpTransport = newCompatibleTransport()
        val service = Sheets.Builder(httpTransport, jsonFactory, credential)
                .setApplicationName(getString(R.string.app_name))
                .build()
        // 書き込む内容を設定
        val rows = listOf<ValueRange>(
                ValueRange()
                        .setRange("'$UNLOCK_LOG_SPREADSHET_SHEETNAME'!A1") // 書き込み先の「シート名!セル」を指定
                        .setValues(listOf(
                                listOf<Any>(mACCOUNT!!.email.toString()) // サインインしたGoogleAccountのメールアドレスを書き込む
                        )),
                ValueRange()
                        .setRange("'$UNLOCK_LOG_SPREADSHET_SHEETNAME'!B1") // 書き込み先の「シート名!セル」を指定
                        .setValues(listOf(
                                listOf<Any>(DateFormat.format("yyyy/MM/dd kk:mm:ss", Date()).toString()) // 日時を書き込む
                        ))
        )
        // 複数範囲への書き込みリクエストの作成
        val body = BatchUpdateValuesRequest()
                .setValueInputOption("RAW")
                .setData(rows)

        // 複数範囲への書き込み処理を実行
        try{
            val result = service.spreadsheets().values().batchUpdate(UNLOCK_LOG_SPREADSHEET_ID, body).execute()
            Log.d("updatedRows", "${result.totalUpdatedRows}")
        }catch (e: java.io.IOException){
            // 例外処理
        }
    }

batchUpdate()に渡す書き込み先のセルや書き込むデータはどのような形で作るのか、結構悩みました。
単一セルを更新することも出来ますが、一気に更新できるなら一気に片づけたいですね。

SpreadSheetから読み出す処理の実装

DriveにあるSpreadSheetから、データを読み出す処理を実装します。

    private fun readEmployeeList():ValueRange {
        // サービスのスコープとしてSpreadSheetsのReadOnlyを設定
        val scopes = listOf(SheetsScopes.SPREADSHEETS_READONLY)
        // GoogleAccountCredentialを生成
        val credential = GoogleAccountCredential.usingOAuth2(applicationContext, scopes)
        // Google Sign-in API実装にて取得したGoogleAccount情報を利用。(社員以外は利用できないように)
        credential.selectedAccount = mACCOUNT!!.account
        // サービス呼び出し
        val jsonFactory = JacksonFactory.getDefaultInstance()
        val httpTransport = newCompatibleTransport()
        val service = Sheets.Builder(httpTransport, jsonFactory, credential)
                .setApplicationName(getString(R.string.app_name))
                .build()
        // データを読み出すSpreadSheetID、シート名、セル範囲を指定
        return service.Spreadsheets().Values().get(EMPLOYEELIST_SPREADSHEET_ID, "'$EMPLOYEELIST_SPREADSHEET_SHEETNAME'!C1:C500").execute()
    }

書き込み処理を実装した後だからか、こちらは比較的簡単に実装できました。

ただ、データ取得範囲を指定してValueRangeで取得するのですが、指定範囲のデータがどのような形で取得できているのか分からなかったため、適当にコードを書いてデバッグで中身を確認してから、値を取り出すコードを実装しなければなりませんでした。

おわりに

この投稿の元になったアプリ開発で、初めてAndroid StudioやKotlinを使って実装しました。

この時点で難航することが目に見えていましたが、Google API、特にGoogle Sheets API v4についてはKotlinの情報が少なく、これだけの実装に1日半も使ってしまいました。

この投稿が、皆さんが「Google Sheets API v4」を使う際に役立てば幸いです。

清水皓生
CSVIT事業部IT部 清水皓生
金融系業務システムのエンジニアをしています。新しい技術や知識の習得のため社内の活動や社外のイベントに顔を出させてもらってます。ストレス解消法は筋トレと単車です。