Task
I need to connect an Android client with a python server using gRPC. Making the server and generating the protos was easy in Python, but the lack of tutorials and confusing documentation for the Kt client makes it appear overwhelmingly complicated.
Background
Until now I've made some simple Android apps using Kotlin, I got used to adding dependencies to either the module or app level build.gradle.
What have I tried?
My first thought was to go to the official documentation as I did with Python. I found the guide from there pretty confusing (I felt like there's something missing from that article), so I went to see the full examples from their GitHub. I also cloned the repo and compiled the protos with the gradlew installDist command. Then the things got awfully complicated:
- When you create an Android Studio project, you get a bunch of gradle things(module and app level build.gradle's, gradlew and gradlew.bat, settings, etc)
- After you clone the repo, you get another bunch of gradle things inside the grpc-kotlin folder.
- You also get build.gradle.kts which seem to be the same build logic/package manager helper files, but with other dependencies and with the Kotlin Script syntax.
This is when I went off to YouTube in order to search for a simple implementation and found out that there's only a handful of videos on the gRPC with Kotlin subject, and most of those are presentation videos about the features of gRPC in Kotlin when using Coroutines.
What I have until now
I migrated all my build.gradle's to .kts ones. This is how my module-level build.gradle.kts looks like:
buildscript {
val kotlin_version = "1.5.10"
repositories {
google()
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:4.2.1")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlin_version}")
classpath("org.jetbrains.kotlin:kotlin-android-extensions:${kotlin_version}")
classpath("com.google.gms:google-services:4.3.8")
classpath ("com.google.protobuf:protobuf-gradle-plugin:0.8.14")
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
tasks.register("clean",Delete::class){
delete(rootProject.buildDir)
}
This is how my app level build.gradle.kts looks like:
import com.google.protobuf.gradle.generateProtoTasks
import com.google.protobuf.gradle.id
import com.google.protobuf.gradle.plugins
import com.google.protobuf.gradle.protobuf
import com.google.protobuf.gradle.protoc
plugins {
id("com.android.application")
id("com.google.protobuf")
kotlin("android")
}
android {
compileSdkVersion(30)
buildToolsVersion = "30.0.3"
defaultConfig {
applicationId = "com.example.myapplication"
minSdkVersion(26)
targetSdkVersion(30)
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
getByName("release") {
isMinifyEnabled = false
proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures {
viewBinding = true
}
}
protobuf {
protoc { artifact = "com.google.protobuf:protoc:3.12.0" }
plugins {
id("grpc") {
artifact = "io.grpc:protoc-gen-grpc-java:1.35.0"
}
}
generateProtoTasks {
all().forEach { task ->
task.plugins.create("java") {
option("lite")
}
task.plugins {
id("grpc") {
this.option("lite")
}
}
}
}
}
dependencies {
val kotlin_version = "1.5.10"
implementation("org.jetbrains.kotlin:kotlin-stdlib:${kotlin_version}")
implementation("androidx.core:core-ktx:1.5.0")
implementation("androidx.appcompat:appcompat:1.3.0")
implementation("com.google.android.material:material:1.3.0")
implementation("androidx.constraintlayout:constraintlayout:2.0.4")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.2")
androidTestImplementation("androidx.test.espresso:espresso-core:3.3.0")
// GRPC Deps
implementation("io.grpc:grpc-okhttp:1.37.0")
implementation("io.grpc:grpc-protobuf-lite:1.37.0")
implementation("io.grpc:grpc-stub:1.36.0")
implementation("org.apache.tomcat:annotations-api:6.0.53")
}
I could generate the protos but something was off about them.
Problem
When implementing the request functions, respectively a bi-directional stream, I found out that all my rpc functions asked for an extra StreamObserver parameter(which was absent in all of the tutorials I've found on the internet). At a closer look I observed that all the generated files were in java and on the official docs, the generated files are both POJOs and Kotlin.
This is how my generated Stub class looks like:
public static final class ChatServiceStub extends io.grpc.stub.AbstractAsyncStub<ChatServiceStub> {
private ChatServiceStub(
io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
super(channel, callOptions);
}
@java.lang.Override
protected ChatServiceStub build(
io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
return new ChatServiceStub(channel, callOptions);
}
/**
* <pre>
* This bi-directional stream makes it possible to send and receive Notes between 2 persons
* </pre>
*/
public void chatStream(grpc.Chat.Empty request,
io.grpc.stub.StreamObserver<grpc.Chat.Note> responseObserver) {
io.grpc.stub.ClientCalls.asyncServerStreamingCall(
getChannel().newCall(getChatStreamMethod(), getCallOptions()), request, responseObserver);
}
/**
*/
public void sendNote(grpc.Chat.Note request,
io.grpc.stub.StreamObserver<grpc.Chat.Empty> responseObserver) {
io.grpc.stub.ClientCalls.asyncUnaryCall(
getChannel().newCall(getSendNoteMethod(), getCallOptions()), request, responseObserver);
}
}
I do not know how to replicate a gradle script for my project, I found no one on the internet explaining how are all those build.gradle's linked together(I figured out that module level build.gradle's are describing how the module they're in is supposed to build and app level build.gradle's are idem but for the entire app). Most of the articles I found are the same as the official docs.
What I want
I just want a simple-simple project or a step by step tutorial, without "clone this and run a command in the terminal, it just works".
I do not blame the devs or whoever wrote the official docs, I actually bet I'm the stupid one here, but I struggle to understand these concepts and I would be grateful if someone can explain to me what I did wrong or where to learn.
Also, sorry for the long question, I tried to expose my POV the best I could, this is my second question since I started learning programming and I'm sorry if the problem and my goals aren't clear enough, I'll edit anything if it's needed.
I do not have a step-by-step process I can share and do not want to trivialize an excellently asked question. However, I wanted to respond that in researching a similar problem, I found that Square has a library that seems to be more Kotlin friendly:
https://square.github.io/wire/#wire-kotlin