KotlinとAndroidでgRPCクライアントを開発する――gRPCサービスの相互運用スキマ時間にこっそり学ぶ「gRPC」入門(6)

第6回は、第4回で実装したサーバストリーミングgRPCサービスを利用するモバイルアプリケーションを、Android OS用にKotlinで開発します。ここでは、gRPCとモバイルアプリケーションの相性などを理解し、異なるプラットフォームとプログラミング言語で構成されるサービスを問題なく利用できることを理解します。

» 2023年04月07日 05時00分 公開

この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。

「スキマ時間にこっそり学ぶ「gRPC」入門」のインデックス

連載:スキマ時間にこっそり学ぶ「gRPC」入門

 本連載のサンプルコードをGitHubで公開しています。こちらからダウンロードしてみてください。


今回のテーマ

 今回のテーマは、KotlinによるgRPCクライアントAndroidアプリの開発です。連載第4回で、PythonによるサーバサイドストリーミングのgRPCサービスを開発しましたが、そのサーバにAndroidアプリからクライアントとしてアクセスします。連載第1回で紹介したように、gRPCのサービスはプログラミング言語やプラットフォームに依存しない設計となっています。今回は、サーバがPython、クライアントがAndroidとして、問題なく利用できることを確認します。

図1 書籍情報検索サービスBookInfoの構成

 サーバサイドストリーミング、そしてPythonによるgRPCサービスの開発については、連載第4回を参照してください。サーバも、連載第4回のものをそのまま利用します。

開発のための準備

 開発に必要な環境を準備しておきます。今回はクライアントがAndroidアプリとなるので、Androidアプリの開発環境を作っていきます。サーバは同一ホストで動作させます。Androidアプリの開発には幾つかの方法がありますが、今回はgRPC公式リポジトリから入手できるgrpc-kotlinをベースに、コードエディタとCLIツールを使って開発していきます。以下で紹介するソフトウェアも、基本的にこの方針に沿ったものとなっています。

必要なソフトウェアをインストールする

 本連載で共通で使用するコードエディタVisual Studio Code(以降、VSCode)をはじめとして、必要なソフトウェアをインストールしておいてください。

  • コードエディタとしてのVisual Studio Code
  • リポジトリクローンのためのGit
  • Kotlin(1.3以降)
  • JDK(7以降)
  • Android SDK(API level 16以降)

 このうち、Kotlin、JDK、Android SDKについては、Android Studioによってまとめてインストールが可能です。本稿ではAndroid Studioは使用しませんが、環境の整備を簡略化できますので、個別のインストールが困難な場合には利用を検討してください。

Download Android Studio & App Tools - Android Developers

 また、本稿ではAndroidアプリの動作をAndroid仮想デバイス(AVD)上で確認します。スペックは特に問わないので、API level 16以降のAVDをあらかじめ作成しておき、デバイス名を控えておいてください。

リポジトリgrpc-kotlinをクローンする

 ソフトウェアのインストールが済んだら、VSCodeのメニューから[ターミナル]−[新しいターミナル]を選択し、ターミナルを開いて作業します。本連載で共通して利用するフォルダatmarkit_grpcにカレントフォルダをあらかじめ移動しておきます。そして、公式サンプルであるgrpc-kotlinをgRPC公式リポジトリからクローンします。

% git clone https://github.com/grpc/grpc-kotlin

 grpc-kotlinは、多数のサブプロジェクトからなるプロジェクトです。以下は、その代表的なものです。

  • android……Android版クライアントアプリのプロジェクト
  • client……CLIベースのクライアントプログラムのプロジェクト
  • server……CLIベースのサーバプログラムのプロジェクト
  • protos……プロトコル定義ファイルのプロジェクト
  • stub……スタブ(クライアント)のプロジェクト
  • stub-android……Android版スタブ(クライアント)のプロジェクト

 全てを利用するわけではないですが、CLIベースのgRPCサーバとクライアントも含まれているので、KotlinによるgRPCサービスの基本的な実装を知るのに有用です。チュートリアルを参考に、CLIベースのプログラムも実行、確認してみるとよいでしょう。

Kotlin | gRPC

 クローン後は、grpc-kotlin/examplesフォルダに移動して作業します。ターミナル上でCLIコマンドによる操作が基本ですが、必要に応じて(フォルダの作成やファイルのコピーなど)VSCodeのエクスプローラーでの操作を併用すると楽でしょう。

既定のAndroidアプリの実行

 BookInfoサービスのクライアントであるAndroidアプリの開発の前に、既定のAndroidアプリをビルドして実行してみましょう。これを通じて、grpc-kotlinにおけるビルドや実行のイメージをつかんでください。

既定のgRPCサーバをビルドする

 既定のAndroidアプリは、既定のgRPCサーバを使用します。ですので、まずはgRPCサーバをビルドしておきます。ビルドは、プロジェクトに用意されたgradlewコマンドによって実行します。Windows環境ではgradlew.batの実行になるので、「./」は不要です。

% ./gradlew installDist

「BUILD SUCCESSFUL」と緑色で出力されれば成功です。この時点で、server、protos、stubの3つのプロジェクトがビルドされています。

既定のAndroidアプリをビルドする

 既定のAndroidアプリであるandroidプロジェクトは、helloworldサービスのクライアントです。androidプロジェクトをビルドして、アプリをAVDにインストールします。ビルドに先立ち、Android SDKのあるパスを環境変数ANDROID_SDK_ROOTに設定し、AVDを起動しておきます。Android StudioでAndroid SDKをインストールした場合には、Android SDKのパスはSDK Managerの[Android SDK Location]から確認できます。emulatorコマンドとadbコマンドは、既定ではパスが通っていないので、フルパスで指定しています。ユーザー名naoは読者の環境に合わせて変更してください。

図2 SDK Manager
% export ANDROID_SDK_ROOT="/Users/nao/Library/Android/sdk"
% /Users/nao/Library/Android/sdk/emulator/emulator -avd Pixel_4_API_30
% ./gradlew :android:installDebug
…略…
% /Users/nao/Library/Android/sdk/platform-tools/adb install android/build/outputs/apk/debug/android-debug.apk

 サーバは、以下のように起動します。

% ./server/build/install/server/bin/hello-world-server
Server started, listening on 50051

 AVDでgRPC Kotlin Androidアプリを起動し、テキストボックスに適当な名前を入れて[Send gRPC Request]をクリックし、以下のようにレスポンスが表示されれば成功です。

図3 androidアプリの実行結果

 動作確認が済んだら、Ctrl+Cを入力してサーバを停止させておいてください。なお、既定のAndroidアプリはAVDで動作させることを想定しています。実機にインストールして実行するには、サーバURLの変更が環境に合わせて必要になってきますが、それについては割愛します。

書籍情報検索クライアントの実装

 既定のAndroidアプリの動作確認が済んだところで、BookInfoサービスのクライアントをAndroidアプリとして実装していきます。作業の内容は、grpc-kotlinにBookInfoサービスのためのファイルを追加し、既存のファイルを複製してそれを修正していくというイメージです。

protosプロジェクトにBookInfoサービスを追加する

 プロトコル定義ファイルのプロジェクトprotosに、BookInfoサービスを既存のサービスに倣って追加します。protos/src/main/proto/io/grpc/examplesフォルダにbookinfoフォルダを作成し、以下のファイルをコピーします。

  • 連載第4回のbookinfo.protoファイル
  • protos/src/main/proto/io/grpc/examples/routeguide/BUILD.bazelファイル

 bookinfo.protoファイルには、既存のpackage文のあとにoption文を2つ追加します。

…略…
package bookinfo;
option java_package = "io.grpc.examples.bookinfo";	(1)
option java_multiple_files = true;	(2)
…略…
protos/src/main/proto/io/grpc/examples/bookinfo/bookinfo.proto

 (1)は、gRPCサービスのパッケージ名bookinfoとは別に、生成されるKotlinのクラスの名前空間を指定するためのものです。(2)は、メッセージのファイルを分割して複数にしてもよいという指定です。指定しないと、一つのファイルにまとめられます。

 BUILD.bazelファイルは、ビルドツールBazelのための設定ファイルです。ここでは修正内容は割愛しますので、配布サンプルを参照してください。基本的に、ファイル中の「route_guide」を「bookinfo」に修正しただけの内容となっています。

 ここでいったんビルドして、追加したプロトコル定義ファイルから正しくソースファイルやクラスファイルが生成されるか確認しておきます。

% ./gradlew installDist

 例えば、stub/build/generated/source/proto/main/grpckt/io/grpc/examples/bookinfo/BookinfoGrpcKt.ktファイルが生成され、そこにはプロトコル定義ファイルにおけるMultiBookInfoサービスの実装が記述されています。

androidプロジェクトを複製してBookInfoクライアント化する

 BookInfoサービス対応のAndroidアプリを作成していきます。既存のandroidフォルダをコピーして、これを改変していくことで作成します。フォルダをコピー後、andoid-bookinfoにリネームします。ここから、android-bookinfoプロジェクト内のファイルを一つずつ修正していきます。

  • ビルドスクリプトの修正(build.gradle.ktsファイル)

 ビルドスクリプトであるandroid-bookinfo/build.gradle.ktsファイルを以下のように修正します。applicationIdの修正のみです。

…略…
defaultConfig {
    applicationId = "io.grpc.examples.bookinfo"		修正
…略…
android-bookinfo/build.gradle.kts
  • マニフェストファイルの修正(AndroidManifest.xmlファイル)

 マニフェストファイルであるandroid-bookinfo/src/main/AndroidManifest.xmlファイルを以下のように修正します。パッケージの変更に合わせた修正のみです。

<?xml version="1.0" encoding="utf-8"?>
<<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="io.grpc.examples.bookinfo">	修正
    …略…
    <application
        …略…
        <activity
                android:theme="@style/Theme.AppCompat.NoActionBar"
                android:name="io.grpc.examples.bookinfo.MainActivity"	修正
…略…
android-bookinfo/src/main/AndroidManifest.xml
  • 文字列リソースファイルの修正(strings.xmlファイル)

 アクティビティーで使用する文字列リソースを以下のように修正、追加します。

<string name="name_hint">検索キーワード</string>
<string name="send_request">検索開始</string>
<string name="app_label">gRPC BookInfo Kotlin Android</string>
<string name="not_found">検索結果が見つかりません。</string>
android-bookinfo/src/main/res/values/strings.xml
  • アクティビティーファイルの修正(MainActivity.ktファイル)

 アクティビティーファイルMainActivity.ktの修正に先立ち、パッケージ名の変更に伴いフォルダ名をhelloworldからbookinfoにリネームしておきます。そして、MainActivity.ktファイルを以下のように修正します。元のファイルから変更しない箇所は省いていますので、必要に応じて公式サンプルも参照してください。

 アクティビティーファイルは、大きく(3)からのアクティビティークラスの定義、(7)からの手続き呼び出しクラスの定義、(13)からのUI構築関数に分かれます。

// 名前空間の変更
package io.grpc.examples.bookinfo	(1)
…略…
// Jetpack Composeのためのインポート
import androidx.compose.foundation.layout.Spacer	(2)
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
// アクティビティークラスの定義
class MainActivity : AppCompatActivity() {	(3)
    …略…
    // サービス呼び出しのためのクラスをインスタンス化
    private val bookinfoService by lazy { MultiBookInfoRCP(uri) }	(4)
    // アクティビティー作成時に呼び出されるメソッド
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // Jetpack Composeでコンテンツを描画
        setContent {		(5)
            Surface(color = MaterialTheme.colors.background) {
                MultiBookInfo(bookinfoService)
            }
        }
    }
    // アクティビティー破棄時に呼び出されるメソッド
    override fun onDestroy() {
        super.onDestroy()
        // サービスをクローズ
        bookinfoService.close()		(6)
    }
}
android-bookinfo/src/main/kotlin/io/grpc/examples/bookinfo/MainActivity.kt

 (1)では、BookInfoサービスに合わせてパッケージ名を修正しています。

 (2)では、UI構築関数で使用するJetpack Compose(後述)の名前空間を追加でインポートしています。

 (3)からは、アクティビティーのクラスです。(4)で、サービス呼び出しのためのクラスをインスタンス化しています。

 (5)は、アクティビティーの作成時に呼び出されますが、ここのsetContentメソッドによってUIが構築されています。これはJetpack Composeと呼ばれる機能で、コードによってUIを構築する新しい仕組みです。ここでは、Surfaceメソッドでコンテンツの背景を指定し、内容についてはMultiBookInfo関数にさらに委ねている、と理解してください。

 (6)は、(4)でインスタンス化したサービスをクローズしています。

// サービス呼び出しのためのクラス
class MultiBookInfoRCP(uri: Uri) : Closeable {		(7)
    // 状態保持、検索結果保持のためのインスタンスを生成
    val responseState = mutableStateOf("")		(8)
    val list = arrayListOf("")
    // サービスのためのチャネルを生成
    private val channel = let {		(9)
         …略…
    }
    // スタブ(クライアント)の生成
    private val bookinfo = MultiBookInfoGrpcKt.MultiBookInfoCoroutineStub(channel)	(10)
    // Search手続きを呼び出すメソッド
    suspend fun search(text: String) {		(11)
        try {
            var count = 0
            list.clear()
            val request = searchRequest { this.text = text }
            // Search手続きを呼び出して結果をイテレータで受け取る
            bookinfo.search(request).collect { searchResponse -> 	(12)
                list.add(searchResponse.item.title)
                count += 1
            }
            responseState.value = "${count} 個が見つかりました。"
        } catch (e: Exception) {
            responseState.value = e.message ?: "Unknown Error"
            e.printStackTrace()
        }
    }
    …略…
}
android-bookinfo/src/main/kotlin/io/grpc/examples/bookinfo/MainActivity.kt

 (7)からは、サービス呼び出しのためのクラスの定義です。このクラスのインスタンスで、サービスへ接続したり、手続きを呼び出したりします。(8)では、手続き呼び出し結果を保持するMutableStateと、Search手続き呼び出しで得られた結果を保持するArrayListを宣言しています。

 (9)では、処理内容は省略していますがサーバへのチャネルを生成しています。(10)では、このチャネルを用いてスタブ(クライアント)オブジェクトを取得しています。実際の通信は、このスタブでやりとりします。

 (11)は、Search手続きを呼び出すメソッドとなります。処理としてはシンプルで、(12)でスタブ経由でSearch手続きを呼び出し、collectメソッドによって返されるイテレータによってArrayListに結果を追加していく、というものです。正常に処理されれば、取得件数をステータスにセットし、例外が発生すればエラーメッセージをステータスにセットします。ここでセットしたステータスは、UI側で参照されて表示内容が切り替わります。

// Jetpack ComposeによるUI構築関数
@Composable							(13)
fun MultiBookInfo(multiBookInfoRCP: MultiBookInfoRCP) {
    val scope = rememberCoroutineScope()
    val nameState = remember { mutableStateOf(TextFieldValue()) }
    // Columnメソッドによって内部のコンポーネントを縦方向に配置
    Column(Modifier.fillMaxWidth().fillMaxHeight(), Arrangement.Top, Alignment.CenterHorizontally) {
        // TextコンポーネントとTextFieldコンポーネントを配置
        Text(stringResource(R.string.name_hint), modifier = Modifier.padding(top = 10.dp))
        OutlinedTextField(nameState.value, { nameState.value = it })
        // Buttonコンポーネントを配置、タップ時にSearch手続きを呼び出す
        Button({ scope.launch { multiBookInfoRCP.search(nameState.value.text) } }, Modifier.padding(10.dp)) {
            Text(stringResource(R.string.send_request))
        }
        // ステータスが有効ならTextコンポーネントにステータスをセット
        if (multiBookInfoRCP.responseState.value.isNotEmpty()) {
            Text(multiBookInfoRCP.responseState.value)
        }
        // LazyColumnメソッドで縦方向のリストを生成
        LazyColumn(modifier = Modifier.padding(all = 8.dp)) {
            // itemsでArrayListの各要素を順番に処理
            items(multiBookInfoRCP.list) { item ->
                // 各要素はシェイプで囲ったTextコンポーネントで表示
                Surface(shape = MaterialTheme.shapes.medium, elevation = 1.dp) {
                    Text(item, modifier = Modifier.padding(all = 4.dp))
                }
                Spacer(modifier = Modifier.height(4.dp))
            } 
        }
    }
}
android-bookinfo/src/main/kotlin/io/grpc/examples/bookinfo/MainActivity.kt

 (13)からは、UI構築関数です。@Composableというアノテーションがあるように、Jetpack Composeによる処理対象となることが明示されています。Jetpack Composeでは、従来のXMLファイルによるUI構築ではなく、コードによって動的にUIを生成します。ビューに配置するコンポーネントの指定や、タップなどのイベントに反応する処理など、これらを関数の処理内容として記述していきます。

 大まかな処理内容は、コメントを参照してください。本稿はJetpack Composeの紹介が目的ではないので詳細は省略しますが、Jetpack Composeのチュートリアルが公開されていますので、合わせて参照してください。

Jetpack Compose チュートリアル - Android Developers

プロジェクトのビルド設定の修正(settings.gradle.ktsファイル)

 最後に、プロジェクトのルートにあるsettings.gradle.ktsファイルを以下のように修正します。これは、サブプロジェクトにandroid-bookinfoを加える設定変更です。

…略…
include("protos", "stub", "stub-lite", "client", "native-client", "server", "stub-android", "android", "android-bookinfo")
…略…
settings.gradle.kts

 android-bookinfoをビルドして、生成されたapkファイルをAPKにインストールします。

% ./gradlew :android-bookinfo:installDebug
% /Users/nao/Library/Android/sdk/platform-tools/adb install android-bookinfo/build/outputs/apk/debug/android-bookinfo-debug.apk

 別ターミナルを開き、連載第4回で紹介したPythonによるBookInfoサーバを起動してください。そして、AVD上でgRPC BookInfo Kotlin Androidアプリを起動します。[キーワード]テキストボックスに適当なキーワードを入力してボタンをクリックし、検索結果がリスト形式で表示されれば成功です。

図4 BookInfoクライアントアプリ

まとめ

 今回は、書籍情報検索サービスのクライアントを、KotlinによるAndroidアプリとして開発してみました。サーバはPythonで開発したものですが、問題なく利用できることを紹介しました。

 次回は、SwiftでgRPCクライアントを開発してみます。

筆者紹介

WINGSプロジェクト

有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティー(代表山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手掛ける。2021年10月時点での登録メンバーは55人で、現在も執筆メンバーを募集中。興味のある方は、どしどし応募頂きたい。著書、記事多数。

サーバーサイド技術の学び舎 - WINGS(https://wings.msn.to/
RSS(https://wings.msn.to/contents/rss.php
Twitter: @yyamada(https://twitter.com/yyamada
Facebook(https://www.facebook.com/WINGSProject


Copyright © ITmedia, Inc. All Rights Reserved.

スポンサーからのお知らせPR

注目のテーマ

Microsoft & Windows最前線2025
AI for エンジニアリング
ローコード/ノーコード セントラル by @IT - ITエンジニアがビジネスの中心で活躍する組織へ
Cloud Native Central by @IT - スケーラブルな能力を組織に
システム開発ノウハウ 【発注ナビ】PR
あなたにおすすめの記事PR

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。