Anyone help me to understand how can I generate protobuf in Kotlin?
I heard about gRPC
, wire
, KotlinPoet
, but I don't understand what are the differences, which one should I use any samples, any simple documents please fill free to share with me?
Can anyone provide a link for a sample which shows how to generate Protobuf sample to Kotlin?
1 Answers
I've used both square/wire
and io.grpc
libraries to communicate with a gRPC service. The problem with the wire
is that it does not support the proto3 version yet.
Here, I'll provide you an example of how to start with io.grpc
.
Suppose that there is a gRPC service which sums up the stream of numbers you're sending to it. Its proto file is gonna be something like:
accumulator.proto
syntax = "proto3";
package accumulator;
service Accumulator {
rpc NumberStream (stream NumberRequest) returns (stream AccumulateReply) {
}
}
message NumberRequest {
int32 number = 1;
}
message AccumulateReply {
int64 sumUp = 1;
}
You should put this file under /src/main/proto/
directory of the project.
Now it's time to add the required dependencies to build.gradle
file. Note that it uses kapt
to generate codes.
App Level's build.gradle
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'com.google.protobuf'
apply plugin: 'kotlin-kapt'
android {
... others
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8
}
}
protobuf {
protoc { artifact = 'com.google.protobuf:protoc:3.10.0' }
plugins {
javalite { artifact = "com.google.protobuf:protoc-gen-javalite:3.0.0" }
grpc {
artifact = 'io.grpc:protoc-gen-grpc-java:1.25.0' // CURRENT_GRPC_VERSION
}
}
generateProtoTasks {
all().each { task ->
task.plugins {
javalite {}
grpc { // Options added to --grpc_out
option 'lite'
}
}
}
}
}
dependencies {
... other dependencies
// ------- GRPC
def grpc_version = '1.25.0'
implementation "io.grpc:grpc-android:$grpc_version"
implementation "io.grpc:grpc-okhttp:$grpc_version"
implementation "io.grpc:grpc-protobuf-lite:$grpc_version"
implementation "io.grpc:grpc-stub:$grpc_version"
// ------- Annotation
def javax_annotation_version = '1.3.2'
implementation "javax.annotation:javax.annotation-api:$javax_annotation_version"
}
Project Level's build.gradle
buildscript {
repositories {
google()
jcenter()
}
dependencies {
... others
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.10'
}
}
Here is a class that encapsulates stream activities with the server. It returns received values through a callback:
AccumulatorHandler.kt
import android.content.Context
import io.grpc.ManagedChannel
import io.grpc.android.AndroidChannelBuilder
import io.grpc.stub.ClientCallStreamObserver
import io.grpc.stub.StreamObserver
import accumulator.AccumulatorOuterClass
import java.util.concurrent.Executors
/**
* @author aminography
*/
class AccumulatorHandler constructor(
private val context: Context,
private val endPoint: String
) {
var callback: AccumulatorCallback? = null
private var managedChannel: ManagedChannel? = null
private var requestObserver: StreamObserver<AccumulatorOuterClass.NumberRequest>? = null
private val responseObserver: StreamObserver<AccumulatorOuterClass.AccumulateReply> =
object : StreamObserver<AccumulatorOuterClass.AccumulateReply> {
override fun onNext(value: AccumulatorOuterClass.AccumulateReply?) {
callback?.onReceived(value.sumUp)
}
override fun onError(t: Throwable?) {
callback?.onError(t)
}
override fun onCompleted() {
callback?.onCompleted()
}
}
fun offer(number: Int) {
initChannelIfNeeded()
requestObserver?.onNext(
AccumulatorOuterClass.NumberRequest.newBuilder()
.setNumber(number)
.build()
)
}
fun offeringFinished() {
requestObserver?.onCompleted()
}
private fun initChannelIfNeeded() {
if (managedChannel == null) {
managedChannel = AndroidChannelBuilder.forTarget(endPoint)
.context(context)
.usePlaintext()
.executor(Executors.newSingleThreadExecutor())
.build()
}
if (requestObserver == null) {
requestObserver = AccumulatorGrpc.newStub(managedChannel)
.withExecutor(Executors.newSingleThreadExecutor())
.numberStream(responseObserver)
}
}
fun release() {
(requestObserver as? ClientCallStreamObserver<*>)?.cancel("Cancelled by client.", null)
requestObserver = null
managedChannel?.shutdown()
managedChannel = null
callback = null
}
interface AccumulatorCallback {
fun onReceived(sum: Long)
fun onError(t: Throwable?)
fun onCompleted()
}
}
In order to test it, I've written an activity class to show its usage in a simple manner:
MyActivity.kt
/**
* @author aminography
*/
class MyActivity: AppCompatActivity, AccumulatorHandler.AccumulatorCallback {
private var accumulatorHandler: AccumulatorHandler? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
accumulatorHandler = AccumulatorHandler(applicationContext, "http://...")
accumulatorHandler.callback = this
for (number in 1..10){
accumulatorHandler.offer(number)
}
accumulatorHandler.offeringFinished()
}
override fun onReceived(sum: Long) {
Toast.makeText(this, "onReceived: $sum", Toast.LENGTH_SHORT).show()
}
override fun onError(t: Throwable?) {
Toast.makeText(this, "onError: $t", Toast.LENGTH_SHORT).show()
accumulatorHandler.release()
}
override fun onCompleted() {
Toast.makeText(this, "onCompleted", Toast.LENGTH_SHORT).show()
accumulatorHandler.release()
}
}

- 21,986
- 13
- 70
- 74
-
Hey, thanks for answering, I don't see where are you adding dependencies to wire? – Artur A Jun 28 '20 at 09:36
-
1And I see you are using in Kotlin but wondering the class that generated for accumulator.proto is it java or Kotlin? – Artur A Jun 28 '20 at 09:39
-
You're welcome. I mentioned in the answer, this is an implementation using `io.grpc`. – aminography Jun 28 '20 at 09:40
-
Both `wire` and `io.grpc` generate boilerplate classes in Java. – aminography Jun 28 '20 at 09:41
-
I see, but there is possible to implement in Kotlin as well,yes? – Artur A Jun 28 '20 at 19:50
-
We know that the kotlin and java are interoperable bidirectionally, but kotlin has more capabilities that when they come to java, the syntax gets a bit inconvenient, like extension functions, etc. So, these libraries generate codes in java and this is a choice of them, not us. Therefore we have no control over it to generate codes in kotlin. – aminography Jun 29 '20 at 06:46
-
1Note: Since version 3.3.0, wire officially supports Proto 3! https://github.com/square/wire/blob/master/CHANGELOG.md#version-330 – shoheikawano Oct 31 '20 at 06:43
-
1@shoheikawano: Great! Thanks for announcing here. – aminography Oct 31 '20 at 07:14
-
Wire supports Proto3 now. – oldergod Dec 09 '20 at 21:22