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