androidengineers.Book a session

Network Architecture

gRPC on Android

article20 minHard

gRPC offers type-safe, efficient communication via Protocol Buffers over HTTP/2. On Android it's worth the setup cost when you need bidirectional streaming, strong typing across teams, or significant payload size reduction.

Protocol Buffers: Define Your API

// user.proto
syntax = "proto3";
package com.example;
option java_multiple_files = true;
option java_package = "com.example.grpc";

message UserRequest {
  string user_id = 1;
}

message UserResponse {
  string id = 1;
  string name = 2;
  string email = 3;
}

service UserService {
  rpc GetUser (UserRequest) returns (UserResponse);
  rpc StreamUserUpdates (UserRequest) returns (stream UserResponse);
}

Gradle Setup

// build.gradle.kts (module)
plugins {
    id("com.google.protobuf") version "0.9.4"
}

dependencies {
    implementation("io.grpc:grpc-kotlin-stub:1.4.1")
    implementation("io.grpc:grpc-okhttp:1.62.2")       // transport for Android
    implementation("com.google.protobuf:protobuf-kotlin:3.25.3")
}

protobuf {
    protoc { artifact = "com.google.protobuf:protoc:3.25.3" }
    plugins {
        create("grpc") { artifact = "io.grpc:protoc-gen-grpc-java:1.62.2" }
        create("grpckt") { artifact = "io.grpc:protoc-gen-grpc-kotlin:1.4.1:jdk8@jar" }
    }
    generateProtoTasks {
        all().forEach { task ->
            task.plugins {
                create("grpc")
                create("grpckt")
            }
            task.builtins { create("kotlin") }
        }
    }
}

Creating a Channel & Stub

class GrpcUserRepository(private val host: String, private val port: Int) {

    private val channel: ManagedChannel = ManagedChannelBuilder
        .forAddress(host, port)
        .usePlaintext()     // ⚠️ TLS only in production; use .useTransportSecurity()
        .build()

    private val stub = UserServiceGrpcKt.UserServiceCoroutineStub(channel)

    // Unary call — one request, one response
    suspend fun getUser(userId: String): UserResponse {
        val request = userRequest { this.userId = userId }
        return stub.getUser(request)
    }

    // Server streaming — one request, stream of responses
    fun streamUserUpdates(userId: String): Flow<UserResponse> {
        val request = userRequest { this.userId = userId }
        return stub.streamUserUpdates(request)
    }

    fun shutdown() = channel.shutdown().awaitTermination(5, TimeUnit.SECONDS)
}

Auth Interceptor

class AuthInterceptor(private val tokenProvider: () -> String) : ClientInterceptor {
    override fun <Req, Resp> interceptCall(
        method: MethodDescriptor<Req, Resp>,
        callOptions: CallOptions,
        next: Channel
    ): ClientCall<Req, Resp> {
        return object : ForwardingClientCall.SimpleForwardingClientCall<Req, Resp>(
            next.newCall(method, callOptions)
        ) {
            override fun start(responseListener: Listener<Resp>, headers: Metadata) {
                headers.put(
                    Metadata.Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER),
                    "Bearer ${tokenProvider()}"
                )
                super.start(responseListener, headers)
            }
        }
    }
}

// Apply to channel
val channel = ManagedChannelBuilder
    .forAddress(host, port)
    .intercept(AuthInterceptor { tokenStore.getToken() })
    .build()

Error Handling

gRPC errors come as StatusException:

try {
    val user = stub.getUser(request)
} catch (e: StatusException) {
    when (e.status.code) {
        Status.Code.NOT_FOUND -> showError("User not found")
        Status.Code.UNAUTHENTICATED -> refreshTokenAndRetry()
        Status.Code.UNAVAILABLE -> showOfflineError()
        else -> showGenericError(e.message)
    }
}

gRPC vs REST Trade-offs

FactorgRPCREST/JSON
Payload size~30–40% smaller (binary proto)Larger (text JSON)
StreamingNative (server/client/bidirectional)Polling or WebSocket
Type safetyCompile-time via protoRuntime
ToolingLess ecosystem, harder to debugExcellent (Postman, curl)
Browser supportRequires gRPC-Web proxyNative
Team sizeBetter for large teams with shared protosFine for small teams

Use gRPC when: multiple platforms share proto definitions, you need bidirectional streaming, or payload size is critical (IoT, high-frequency updates).

Use REST when: you want easy debugging, public-facing APIs, or browser clients without a proxy.

YOUR LEARNING JOURNEY

0 of 177 available lessons completed

Progress saved in this browser. No account needed.
gRPC on Android | Android System Design | Android Engineers