---
title: "Drop Envoy: Native gRPC Streaming on Android and iOS with Connect Protocol"
published: true
description: "Eliminate your gRPC-Web proxy by switching to Connect Protocol. Full protobuf codegen, HTTP/2 bidirectional streaming, and composable interceptors — no Envoy sidecar required."
tags: kotlin, android, ios, api
canonical_url: https://mvpfactory.co/blog/grpc-connect-protocol-android-ios
---
## What We Are Building
By the end of this, you will have replaced your Envoy sidecar with Connect Protocol on both Android and iOS — getting native HTTP/2 bidirectional streaming, compile-time type safety from protobuf codegen, and a composable interceptor pipeline for auth, retry, and observability. No proxy. No workarounds.
## Prerequisites
- An existing gRPC or REST API backend
- Android project using Gradle (Kotlin DSL)
- iOS project using Swift Package Manager
- `buf` CLI installed for proto generation
---
## Step 1: Why You Are Paying the Proxy Tax
Standard gRPC-Web forces this architecture:
Mobile client → Envoy (translate framing) → gRPC backend
That Envoy hop is an operational burden — another service to deploy, version, and monitor. gRPC-Web exists because *browsers* cannot speak raw HTTP/2 framing. Mobile apps can. Most teams cargo-cult the proxy anyway.
Connect Protocol, maintained by Buf, fixes this properly. It defines a lightweight envelope over HTTP/1.1 and HTTP/2 that is both browser-friendly and natively gRPC-compatible — no sidecar required.
---
## Step 2: Protobuf Codegen on Android
Here is the minimal setup to get this working in Gradle:
kotlin
// build.gradle.kts
plugins {
id("com.google.protobuf") version "0.9.4"
}
dependencies {
implementation("com.connectrpc:connect-kotlin-okhttp:0.6.0")
implementation("com.connectrpc:connect-kotlin:0.6.0")
}
protobuf {
protoc { artifact = "com.google.protobuf:protoc:3.25.1" }
plugins {
create("connect-kotlin") {
artifact = "com.connectrpc:protoc-gen-connect-kotlin:0.6.0:jvm8@jar"
}
}
generateProtoTasks {
all().forEach { task ->
task.plugins { create("connect-kotlin") }
}
}
}
This generates both protobuf models and Connect-flavored service stubs in a single Gradle task. No separate script step.
> Pin versions against the [Connect-Kotlin releases page](https://github.com/connectrpc/connect-kotlin/releases) before shipping.
---
## Step 3: Codegen on iOS with Swift Package Manager
swift
// Package.swift
dependencies: [
.package(url: "https://github.com/connectrpc/connect-swift", from: "0.12.0"),
],
targets: [
.target(name: "MyApp", dependencies: ["Connect"])
]
Proto generation on iOS uses `buf generate` with the `connect-swift` plugin. Wire it into an Xcode build phase or Makefile pre-build step — it outputs typed `ProtocolClient` stubs directly.
> Pin versions against the [Connect-Swift releases page](https://github.com/connectrpc/connect-swift/releases).
---
## Step 4: Bidirectional Streaming Over HTTP/2
This is the part that makes the switch worth it. Here is a live bidirectional stream in Connect-Kotlin:
kotlin
val stream = client.eliza().converse()
launch {
stream.responseChannel().collect { response ->
println(response.sentence)
}
}
stream.send(ConverseRequest { sentence = "Hello" })
stream.close()
Not long polling. Not chunked SSE. Both send and receive channels are live simultaneously over a single HTTP/2 stream. URLSession on iOS exposes the same primitive via Connect-Swift's `BidirectionalStreamInterface`.
---
## Step 5: The Interceptor Pipeline
Let me show you a pattern I use in every project. Cross-cutting concerns — auth, retry, observability — belong in the protocol layer, not scattered across call sites:
kotlin
val client = ProtocolClient(
httpClient = ConnectOkHttpClient(OkHttpClient()),
config = ProtocolClientConfig(
host = "https://api.example.com",
serializationStrategy = GoogleJavaProtobufStrategy(),
interceptors = listOf(
::AuthInterceptor,
::RetryInterceptor,
::TracingInterceptor
)
)
)
No Envoy filter chains to configure. No sidecar YAML to maintain.
---
## Connect vs REST: Why the Numbers Matter
Google's protobuf benchmarks show serialization running 3–10× faster than JSON, with payloads 20–60% smaller. A typical list of 50 user records runs ~1.2 KB protobuf vs ~3.8 KB JSON — a difference that compounds under mobile data constraints.
| Dimension | OkHttp REST (JSON) | Connect-Kotlin |
|---|---|---|
| Payload size | Verbose JSON | Binary protobuf (~3–5× smaller) |
| Type safety | Runtime (Gson/Moshi) | Compile-time (proto) |
| Streaming | Polling or SSE workarounds | Native bidirectional |
| Code generation | Manual or OpenAPI | `buf generate` → stubs |
| Proxy required | No | No |
HTTP/2 eliminates HTTP-level head-of-line blocking, meaningfully reducing latency variance on lossy LTE connections. TCP-level head-of-line blocking still requires HTTP/3 over QUIC to fully resolve — but removing the HTTP layer alone is a real win.
---
## Gotchas
**Version drift is silent.** The artifact versions in these examples will drift. A mismatched `protoc-gen-connect-kotlin` version generates stubs that compile but misbehave at runtime. Pin against the release pages before shipping.
**Do not commit generated files.** Treat `buf generate` output as a CI artifact. Both Android and iOS can generate typed clients from the same `.proto` source of truth — there is no reason to check in generated stubs.
**Audit your Envoy topology first.** Here is the gotcha that will save you hours: if Envoy exists solely to serve mobile clients rather than browsers, it is a removal candidate. If it serves both, keep it for web and let mobile bypass it with Connect's native HTTP/2 transport.
---
## Conclusion
Connect Protocol is not a workaround — it is the correct architecture for mobile gRPC today. The proxy was always a concession to browser limitations. Mobile clients never had that limitation.
Start by auditing whether Envoy is there for browsers or for mobile. If mobile is the only client, remove it. Wire codegen into CI, centralize cross-cutting concerns in the interceptor chain, and let the type system catch API contract mismatches at compile time instead of at 2 AM on-call.
Top comments (0)