<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Shubhr Modh</title>
    <description>The latest articles on DEV Community by Shubhr Modh (@shubhr_modh_29ab8db84e041).</description>
    <link>https://dev.to/shubhr_modh_29ab8db84e041</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3809780%2F30161c0c-fc0a-4b92-b4f4-d3dfb00a474d.png</url>
      <title>DEV Community: Shubhr Modh</title>
      <link>https://dev.to/shubhr_modh_29ab8db84e041</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shubhr_modh_29ab8db84e041"/>
    <language>en</language>
    <item>
      <title>Building a Shared Networking Layer using ktor in KMP</title>
      <dc:creator>Shubhr Modh</dc:creator>
      <pubDate>Fri, 06 Mar 2026 13:52:15 +0000</pubDate>
      <link>https://dev.to/shubhr_modh_29ab8db84e041/building-a-shared-networking-layer-using-ktor-in-kmp-1knk</link>
      <guid>https://dev.to/shubhr_modh_29ab8db84e041/building-a-shared-networking-layer-using-ktor-in-kmp-1knk</guid>
      <description>&lt;p&gt;Maintaining separate networking layers for Android and iOS often leads to code duplication and synchronisation headaches. By using Ktor, we can build a single, shared codebase for both the frontend networking and the backend API models.&lt;/p&gt;

&lt;p&gt;In this article, I’ll share how I built a shared networking layer for FieldSync, a project that handles authentication through a single reusable codebase.&lt;/p&gt;

&lt;p&gt;The Architecture: One Language, Two Ends&lt;br&gt;
In this project, I implemented an authentication flow (Phone Login &amp;amp; OTP) where the Data Transfer Objects (DTOs) are shared between the Ktor Server and the KMP Mobile app.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Project Overview&lt;br&gt;
The project consists of modules:&lt;br&gt;
• shared: Contains the Ktor HTTP Client and Request/Response models.&lt;br&gt;
• server: A Ktor-based backend handling OTP generation.&lt;br&gt;
• Android/iOS Apps: Consuming the shared repository.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Dependency Setup&lt;br&gt;
First, we ensure our libs.versions.toml and build.gradle.kts are synced. Both the server and the shared module need kotlinx-serialization.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;commonMain.dependencies {
    implementation(libs.kotlinx.coroutines.core)
    implementation(libs.ktor.client.core)
    implementation(libs.ktor.client.content.negotiation)
    implementation(libs.ktor.serialization.kotlinx.json)
    implementation(libs.kotlinx.serialization.json)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;libs.versions.toml:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negotiation", version.ref = "ktor" }
ktor-serverCore = { module = "io.ktor:ktor-server-core-jvm", version.ref = "ktor" }
ktor-serverNetty = { module = "io.ktor:ktor-server-netty-jvm", version.ref = "ktor" }
ktor-serverTestHost = { module = "io.ktor:ktor-server-test-host-jvm", version.ref = "ktor" }
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Backend Implementation (The ktor Server)
The server runs on Netty. To ensure it is accessible to mobile emulators, we bind it to 0.0.0.0.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// server/.../Application.kt
fun main() {
    embeddedServer(Netty, port = SERVER_PORT, host = "0.0.0.0", module = Application::module).start(wait = true)
}

fun Application.module() {
    install(ContentNegotiation) { json() }

    routing {
        get("/") { call.respond(mapOf("status" to "FieldSync backend running")) }
        authRoutes() // Routes for /send-otp and /verify-otp
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Developer “Hack” for OTP: Since third-party SMS gateways (like Twilio or MSG91) aren’t free, I implemented a console based flow for development:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Server generates a random 6-digit code.&lt;/li&gt;
&lt;li&gt;Server prints the code to the IntelliJ Console.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The developer reads the console and types the code into the emulator.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The Shared Networking Layer&lt;br&gt;
The heart of the project is the AuthRepository in the shared module. This is where we handle the platform specific localhost differences.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class AuthRepository {
    private val client = HttpClient {
        install(ContentNegotiation) {
            json(Json { ignoreUnknownKeys = true })
        }
    }

    // Solve the "Localhost" problem
    private val baseUrl: String
        get() = if (getPlatform().name.contains("Android")) {
            "http://10.0.2.2:8080" // Special alias for computer's localhost in Android
        } else {
            "http://localhost:8080" // Standard for iOS Simulator
        }

    suspend fun sendOtp(phone: String): Result&amp;lt;String&amp;gt; {
        return try {
            val response = client.post("$baseUrl/send-otp") {
                contentType(ContentType.Application.Json)
                setBody(SendOtpRequest(phone)) // Shared DTO
            }
            if (response.status == HttpStatusCode.OK) Result.success("Sent") 
            else Result.failure(Exception("Error: ${response.status}"))
        } catch (e: Exception) {
            Result.failure(e)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Challenges Faced
While implementing this architecture, I encountered a few common issues:
A. The “CLEARTEXT” Error
Android blocks http traffic by default. To fix this for local development, I had to update the AndroidManifest.xml:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;application
android:usesCleartextTraffic=”true”&amp;gt;
&amp;lt;/application&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;B. Emulator Networking&lt;br&gt;
New KMP developers often struggle with localhost. I learned that 127.0.0.1 inside an Android emulator points to the emulator itself, not the machine running the ktor server. Using 10.0.2.2 is the bridge to the host machine.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Benefits of This Approach
Single Source of Truth: If the SendOtpRequest model changes, it updates for both the Server and the Apps simultaneously.
Consistent Behavior: Error handling and JSON parsing logic are identical on iOS and Android.
Faster Iteration: You can test your backend logic and UI flow without ever leaving the Kotlin ecosystem.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;br&gt;
Building a shared networking layer with ktor isn’t just about writing less code, it’s about building a more reliable system. By centralising API communication, we reduce the surface area for bugs and ensure that our mobile platforms behave as twins rather than distant cousins.&lt;br&gt;
What’s next? In the next phase, I plan to integrate Firebase Authentication to replace the console based OTP with real SMS delivery and add Token based persistence for user sessions.&lt;/p&gt;

</description>
      <category>kotlin</category>
      <category>ktor</category>
      <category>kmp</category>
      <category>compose</category>
    </item>
  </channel>
</rss>
