DEV Community

vmodal_ai
vmodal_ai

Posted on

Building Android SDKs with Kotlin: API Design, Publishing & Versioning

Building Android SDKs with Kotlin: API Design, Publishing & Versioning

An Android SDK is a reusable software component that other applications integrate into their projects.

Unlike application code, an SDK becomes a public contract.

That means API design, compatibility, documentation, packaging, and versioning are critical.

SDK Architecture

A well-designed SDK can look like:

Host Application
       |
       v
Public SDK API
       |
       v
Internal Services
   /        |        \
Network   Storage   Native APIs
Enter fullscreen mode Exit fullscreen mode

Only the required surface should be public.

Public API Design

Prefer small, stable interfaces.

For example:

interface VModalSdk {
    fun initialize(config: SdkConfig)
    fun start()
    fun stop()
}
Enter fullscreen mode Exit fullscreen mode

Configuration:

data class SdkConfig(
    val apiKey: String,
    val environment: Environment
)
Enter fullscreen mode Exit fullscreen mode

Keep implementation classes internal:

internal class SdkEngine {
    // Internal implementation.
}
Enter fullscreen mode Exit fullscreen mode

Kotlin's internal visibility helps prevent consumers from depending on implementation details.

Initialization

Avoid APIs that require many constructor parameters.

Prefer a configuration object:

data class SdkConfig(
    val apiKey: String,
    val environment: Environment,
    val enableLogging: Boolean = false
)
Enter fullscreen mode Exit fullscreen mode

This makes future additions easier.

Callbacks vs Flow

For simple events:

interface SdkListener {
    fun onConnected()
    fun onDisconnected()
    fun onError(error: SdkError)
}
Enter fullscreen mode Exit fullscreen mode

For modern Kotlin applications, Flow can be useful:

val connectionState: StateFlow<ConnectionState>
Enter fullscreen mode Exit fullscreen mode

Choose based on the consumers your SDK must support.

Avoid Leaking Dependencies

If your SDK internally uses a particular networking or JSON library, avoid forcing consumers to use those implementation types in your public API.

For example, avoid exposing:

fun getResponse(): RetrofitResponse
Enter fullscreen mode Exit fullscreen mode

Prefer:

fun getUser(): SdkUser
Enter fullscreen mode Exit fullscreen mode

This reduces dependency coupling.

Threading Contract

Document which thread callbacks occur on.

A good SDK should define:

Initialization -> background
Network work  -> background
Callbacks     -> main thread
Enter fullscreen mode Exit fullscreen mode

or expose a clear coroutine/Flow contract.

Do not leave threading behavior ambiguous.

Error Design

Create stable SDK-specific errors:

sealed class SdkError {
    data object Unauthorized : SdkError()
    data object Network : SdkError()
    data object InvalidConfiguration : SdkError()
    data class Unknown(val message: String) : SdkError()
}
Enter fullscreen mode Exit fullscreen mode

Consumers can then handle errors without parsing strings.

Android Lifecycle

SDKs must be careful about:

  • Activity references
  • Context leaks
  • background work
  • configuration changes
  • process death

Prefer application context when an Activity context is not required.

Avoid storing Activity references in long-lived objects.

API Compatibility

Once an API is published, consumers may depend on it for years.

Avoid casually changing:

fun start(config: Config)
Enter fullscreen mode Exit fullscreen mode

to:

fun start(newConfig: NewConfig)
Enter fullscreen mode Exit fullscreen mode

Prefer additive changes or introduce a new API.

Semantic Versioning

A common versioning scheme is:

MAJOR.MINOR.PATCH
Enter fullscreen mode Exit fullscreen mode

For example:

2.4.1
Enter fullscreen mode Exit fullscreen mode

Meaning:

2 = major
4 = minor
1 = patch
Enter fullscreen mode Exit fullscreen mode

Use major releases for breaking public API changes.

Minor releases can add backward-compatible features.

Patch releases should normally contain backward-compatible fixes.

Publishing

An Android SDK can be distributed through:

  • Maven Central
  • GitHub Packages
  • a private Maven repository
  • an organization's artifact repository

A typical consumer dependency looks like:

dependencies {
    implementation("com.example:my-sdk:2.4.1")
}
Enter fullscreen mode Exit fullscreen mode

The exact publishing configuration depends on the repository.

Gradle Publishing

A library module normally uses:

plugins {
    id("com.android.library")
    id("org.jetbrains.kotlin.android")
    id("maven-publish")
}
Enter fullscreen mode Exit fullscreen mode

Publishing configuration should produce the required Maven metadata and artifacts.

Keep repository credentials outside source control.

Release Checklist

Before publishing:

[ ] Public API reviewed
[ ] Documentation updated
[ ] CHANGELOG updated
[ ] Tests passing
[ ] Sample application tested
[ ] ProGuard/R8 rules verified
[ ] Version incremented
[ ] Artifact generated
[ ] Publication tested
Enter fullscreen mode Exit fullscreen mode

ProGuard / R8

If your SDK uses reflection, serialization, JNI, or libraries that require keep rules, test release builds carefully.

Provide consumer rules when necessary:

consumer-rules.pro
Enter fullscreen mode Exit fullscreen mode

Do not disable shrinking globally just because one SDK component has a configuration issue.

Documentation

Every public SDK should explain:

  • installation
  • initialization
  • permissions
  • lifecycle
  • threading
  • error handling
  • supported Android versions
  • configuration
  • migration notes
  • sample code

A sample application is often more useful than a large API reference alone.

Backward Compatibility

Maintain a clear policy.

For example:

2.x
  -> backward-compatible feature releases

3.x
  -> breaking API changes
Enter fullscreen mode Exit fullscreen mode

Deprecate before removing public APIs where practical:

@Deprecated(
    message = "Use start(config) instead."
)
fun startLegacy() {
}
Enter fullscreen mode Exit fullscreen mode

Separate Public and Internal Packages

A useful structure:

com.example.sdk
    public API

com.example.sdk.internal
    implementation
Enter fullscreen mode Exit fullscreen mode

Consumers should depend only on the documented public package.

Conclusion

Building an Android SDK is fundamentally different from building an Android application.

The application can change quickly because you control the client. An SDK must assume that unknown developers will depend on its public API.

Therefore, design the public API carefully, hide implementation details, document threading and lifecycle behavior, publish reproducible artifacts, and use disciplined versioning.

Useful Links

SDK Flutter: https://github.com/v-modal/vmodal_sdk_flutter

SDK Android: https://github.com/v-modal/vmodal_sdk_android

Discord: https://discord.gg/K72z28KUx

Top comments (0)