Blog

androidでgrpc-kotlinを使う

サーバ間、サーバとクライアント間の通信で gRPCを採用することは、様々なメリットがあります。

AndroidでもgRPCの採用事例が増えてきたように思います。

以前はgrpc/grpc-javaを使ってstubを生成してたと思うのですが、いつの間にかgrpc/grpc-kotlinも出ていました。

しっかりとkotlin coroutinesをサポートしており、かなり便利だったので、導入方法を紹介します。

1. protoファイルの作成

まずはproto fileを作りましょう。

どこでも良さそうですが、app/src/main/proto に入れています。

java_package は指定しておいたほうがわかりやすいと思います。

検証のため、stream系のrpcも一通り揃えておきました。

// app/src/main/proto/test.proto

syntax = "proto3";
package test;
option java_package = "com.example.test.pb";

service TestService {
    rpc SayHello(HelloRequest) returns (HelloResponse);
    rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse);
    rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse);
    rpc BidiHello(stream HelloRequest) returns (stream HelloResponse);
}

message HelloRequest {
    string message = 1;
}

message HelloResponse {
    string message = 1;
}

2. build.gradleを変更

コード生成もgrdle pluginで行います。

まず、rootのbuild.gradleにpluginを追加

// build.gradle
buildscript {
    ...
    dependencies {
        ...
        classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.8'
    }
}

appのbuild.gradleにapply pluginを追加。

// app/build.gradle
apply plugin: 'com.google.protobuf'
...

更に、依存関係を追加。

// app/build.gradle
...
dependencies {
    ...
    implementation "io.grpc:grpc-okhttp:1.29.0"
    implementation "io.grpc:grpc-protobuf:1.29.0"
    implementation "io.grpc:grpc-stub:1.29.0"
    implementation "io.grpc:grpc-kotlin-stub:0.1.1"
    implementation "javax.annotation:javax.annotation-api:1.3.2"
}

最後にコード生成用のpluginを追加。

android等で使う場合は、liteのoptionを入れておいたほうが良さそうです。

// app/build.gradle
...
protobuf {
    protoc { artifact = 'com.google.protobuf:protoc:3.11.0' }
    plugins {
        grpc {
            artifact = 'io.grpc:protoc-gen-grpc-java:1.29.0'
        }
        grpckt {
            artifact = 'io.grpc:protoc-gen-grpc-kotlin:0.1.1'
        }
    }
    generateProtoTasks {
        all().each { task ->
            task.builtins {
                java { option 'lite' }
            }
            task.plugins {
                grpc { option 'lite' }
                grpckt {}
            }
        }
    }
}

app/src/main/proto 以外にproto fileを置いている場合は、別途フォルダ指定が必要になります。

// app/build.gradle
...
android {
    ...
    sourceSets {
        main {
            proto {
                srcDir 'src/main/protobuf'
            }
        }
    }
}

これで、一度buildすると、app/build/generated/source/proto/debug/grpckt/ あたりにコードが生成されているはずです。

3. stubの利用

まず、チャンネルを作成します。

private const val HOST = "127.0.0.1"
private const val PORT = 8080

val channel = ManagedChannelBuilder
    .forAddress(HOST, PORT)
    .usePlaintext()
    .build()

それを使って、TestServiceCoroutineStub のインスタンスを作成します。

val testService = TestServiceGrpcKt
    .TestServiceCoroutineStub(channel)

1で定義したrpcはそれぞれ以下のようなinterfaceになっており、それぞれkotlin coroutinesを使って呼び出すことができます。

streamがちゃんとflowになっているところが、親切ですね。

class TestServiceCoroutineStub {
    suspend fun sayHello(request: Test.HelloRequest): Test.HelloResponse { ... }

    fun lotsOfReplies(request: Test.HelloRequest): Flow<Test.HelloResponse> { ... }

    suspend fun lotsOfGreetings(requests: Flow<Test.HelloRequest>): Test.HelloResponse { ... }

    fun bidiHello(requests: Flow<Test.HelloRequest>): Flow<Test.HelloResponse> { ... }
}

viewModelScope等を使って呼び出しましょう。

class TestViewModel: ViewModel() {
    private val testSerivce: TestServiceCoroutineStub = ...

    init {
        viewModelScope.launch {
            val request = Test.HelloRequest.newBuilder()
                .setMessage("hello")
                .build()
            val response = testSerivce.sayHello(request)
            ...
        }
    }
}

まとめ

grpc-javaではblockingStubやasyncStubを生成することができますが、kotlin coroutinesを採用しているプロジェクトであれば、いちいちmapperを書かなければならず、少しめんどくさかったように思います。

そこが、最初からsuspend functionやflowを使った形で生成してくれるので、非常に便利になりましたね。

一方で、あくまでもgrpc-javaのwrapperなので、kotlin native等ではまだ使うことができなさそうですね。

requestのbuilderも冗長に感じますし、one of等もsealed class等を使ってもっとシンプルに表現できそうな気がします。

今後の発展にも注目です。

人気の記事

kotlin coroutinesのFlow, SharedFlow, StateFlowを整理する

Jetpack ComposeとKotlin Coroutinesを連携させる

Layout Composableを使って複雑なレイアウトを組む【Jetpack Compose】

テスト用Dispatcherの使い分け【Kotlin Coroutines】

Flow.combineの内部実装がすごい話

Jetpack ComposeのRippleエフェクトを深堀り、カスタマイズも