Mobile Architecture10 min read

Framework Versioning & Distribution for Mobile SDKs

Master semantic versioning, dependency management, and distribution strategies for mobile frameworks and SDKs.

The SDK Versioning Challenge

When you're building mobile SDKs — whether internal frameworks for your white-label apps or public SDKs for third-party developers — versioning becomes critical. A breaking change in a minor version can break dozens of consuming apps. An unclear upgrade path frustrates developers and erodes trust.

This guide covers versioning strategies, distribution mechanisms, and best practices for mobile SDK management.

Semantic Versioning for Mobile

The MAJOR.MINOR.PATCH Contract

MAJOR.MINOR.PATCH

MAJOR: Breaking changes (API removals, behavior changes)
MINOR: New features (backwards compatible)
PATCH: Bug fixes (backwards compatible)

What Constitutes a Breaking Change?

Change TypeBreaking?Version Bump
Remove public methodYesMAJOR
Change method signatureYesMAJOR
Change default behaviorYesMAJOR
Add required parameterYesMAJOR
Add optional parameterNoMINOR
Add new methodNoMINOR
Add new classNoMINOR
Fix bug (same behavior contract)NoPATCH
Performance improvementNoPATCH
Documentation updateNoPATCH

Pre-release Versions

Signal instability with pre-release identifiers:

1.0.0-alpha.1    # Early development
1.0.0-beta.1     # Feature complete, testing
1.0.0-rc.1       # Release candidate
1.0.0            # Stable release

Build Metadata

Add build information without affecting version precedence:

1.0.0+build.123
1.0.0+20241214
1.0.0-beta.1+sha.a1b2c3d

Dependency Specification

Version Constraints

Allow consuming apps to specify acceptable versions:

# CocoaPods
pod 'MySDK', '~> 1.2'      # >= 1.2.0, < 2.0.0
pod 'MySDK', '~> 1.2.3'    # >= 1.2.3, < 1.3.0
pod 'MySDK', '>= 1.2', '< 2.0'

# Swift Package Manager
.package(url: "...", from: "1.2.0")              # >= 1.2.0, < 2.0.0
.package(url: "...", .upToNextMinor(from: "1.2.0"))  # >= 1.2.0, < 1.3.0
.package(url: "...", exact: "1.2.3")             # exactly 1.2.3

Version Resolution

When multiple dependencies require the same SDK:

App depends on:
  ├── LibraryA requires MySDK ~> 1.2
  └── LibraryB requires MySDK ~> 1.4

Resolution: MySDK 1.4.x (satisfies both)

Conflicts occur when ranges don't overlap:

App depends on:
  ├── LibraryA requires MySDK ~> 1.2
  └── LibraryB requires MySDK ~> 2.0

Resolution: CONFLICT (cannot satisfy both)

iOS Distribution Strategies

Swift Package Manager (Preferred)

// Package.swift
let package = Package(
    name: "MySDK",
    platforms: [
        .iOS(.v14),
        .macOS(.v11)
    ],
    products: [
        .library(
            name: "MySDK",
            targets: ["MySDK"]
        ),
    ],
    targets: [
        .target(
            name: "MySDK",
            dependencies: [],
            path: "Sources"
        ),
        .testTarget(
            name: "MySDKTests",
            dependencies: ["MySDK"]
        ),
    ]
)

Distribution:

  • Host on GitHub with tags matching versions
  • Consumers add via Xcode or Package.swift

CocoaPods

# MySDK.podspec
Pod::Spec.new do |s|
  s.name         = 'MySDK'
  s.version      = '1.2.3'
  s.summary      = 'A powerful mobile SDK'
  s.homepage     = 'https://github.com/company/mysdk'
  s.license      = { :type => 'MIT' }
  s.author       = { 'Company' => 'sdk@company.com' }
  s.source       = { :git => 'https://github.com/company/mysdk.git',
                     :tag => s.version.to_s }

  s.ios.deployment_target = '14.0'
  s.swift_version = '5.9'

  s.source_files = 'Sources/**/*.swift'

  s.dependency 'Alamofire', '~> 5.0'
end

Distribution:

  • Push to CocoaPods trunk: pod trunk push MySDK.podspec
  • Or host private spec repo for internal SDKs

XCFramework (Binary Distribution)

#!/bin/bash
# Build XCFramework

xcodebuild archive \
    -scheme MySDK \
    -destination "generic/platform=iOS" \
    -archivePath build/MySDK-iOS \
    SKIP_INSTALL=NO

xcodebuild archive \
    -scheme MySDK \
    -destination "generic/platform=iOS Simulator" \
    -archivePath build/MySDK-Simulator \
    SKIP_INSTALL=NO

xcodebuild -create-xcframework \
    -framework build/MySDK-iOS.xcarchive/Products/Library/Frameworks/MySDK.framework \
    -framework build/MySDK-Simulator.xcarchive/Products/Library/Frameworks/MySDK.framework \
    -output build/MySDK.xcframework

Android Distribution Strategies

Maven Central / JitPack

// build.gradle (library module)
plugins {
    id 'com.android.library'
    id 'maven-publish'
}

android {
    namespace 'com.company.mysdk'
    compileSdk 34

    defaultConfig {
        minSdk 24
        targetSdk 34
    }

    publishing {
        singleVariant("release") {
            withSourcesJar()
            withJavadocJar()
        }
    }
}

publishing {
    publications {
        release(MavenPublication) {
            groupId = 'com.company'
            artifactId = 'mysdk'
            version = '1.2.3'

            afterEvaluate {
                from components.release
            }
        }
    }
}

Distribution:

  • Push to Maven Central for public SDKs
  • Use JitPack for GitHub-hosted projects
  • Host private Maven repository for internal SDKs

AAR Distribution

For binary-only distribution:

// Consumer build.gradle
dependencies {
    implementation files('libs/mysdk-1.2.3.aar')
}

Versioning Automation

Automated Version Bumps

# .github/workflows/version.yml
name: Version Bump

on:
  push:
    branches: [main]

jobs:
  version:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Determine version bump
        id: bump
        run: |
          # Parse conventional commits
          if git log --oneline -1 | grep -q "BREAKING CHANGE\|!:"; then
            echo "type=major" >> $GITHUB_OUTPUT
          elif git log --oneline -1 | grep -q "^feat"; then
            echo "type=minor" >> $GITHUB_OUTPUT
          else
            echo "type=patch" >> $GITHUB_OUTPUT
          fi

      - name: Bump version
        run: |
          # Update version in Package.swift, podspec, build.gradle
          ./scripts/bump-version.sh ${{ steps.bump.outputs.type }}

      - name: Create release
        uses: softprops/action-gh-release@v1
        with:
          tag_name: v${{ env.NEW_VERSION }}
          generate_release_notes: true

Changelog Generation

Generate changelogs from commit history:

# Changelog

## [1.3.0] - 2024-12-14

### Added
- New authentication API (#123)
- Support for biometric login (#124)

### Changed
- Improved error handling in network layer (#125)

### Fixed
- Memory leak in image caching (#126)

## [1.2.1] - 2024-12-01

### Fixed
- Crash on iOS 14 devices (#120)

Deprecation Strategy

Marking Deprecated APIs

// Swift
@available(*, deprecated, message: "Use newMethod() instead")
public func oldMethod() {
    newMethod()
}

@available(*, deprecated, renamed: "NewClass")
public typealias OldClass = NewClass
// Kotlin
@Deprecated(
    message = "Use newMethod() instead",
    replaceWith = ReplaceWith("newMethod()")
)
fun oldMethod() {
    newMethod()
}

Deprecation Timeline

v1.0.0: oldMethod() introduced
v1.2.0: oldMethod() deprecated (warning)
v1.4.0: oldMethod() still works (reminder in release notes)
v2.0.0: oldMethod() removed (breaking change)

Migration Guides

For major version upgrades, provide clear migration guides:

Example: Migrating from 1.x to 2.0

Breaking Changes

Authentication API: The login(username:password:) method has been replaced with authenticate(credentials:).

Before (1.x):

sdk.login(username: "user", password: "pass")

After (2.0):

let credentials = Credentials(username: "user", password: "pass")
sdk.authenticate(credentials: credentials)

Networking Layer: The synchronous network methods have been removed. Use async/await.

Before (1.x):

let result = sdk.fetchData() // Blocking

After (2.0):

let result = await sdk.fetchData() // Async

Compatibility Matrix

Document compatibility clearly:

SDK VersioniOSAndroidXcodeKotlin
2.0.x15.0+API 26+15.0+1.9+
1.5.x14.0+API 24+14.0+1.8+
1.4.x13.0+API 23+13.0+1.7+

Best Practices

1. Version Everything

  • Source code
  • Documentation
  • Sample apps
  • API specifications

2. Test Upgrade Paths

Automated tests that verify migration from version N to N+1.

3. Communicate Early

Announce deprecations at least two minor versions before removal.

4. Maintain LTS Versions

For enterprise customers, maintain long-term support branches:

  • 1.x LTS: Security fixes until 2025-12-31
  • 2.x Current: Active development

5. Binary Compatibility

For compiled frameworks, test binary compatibility across Xcode/Android Studio versions.

Conclusion

Good versioning is invisible — consumers trust that updates won't break their apps. Bad versioning erodes trust and creates friction.

Key principles:

  • Semantic versioning is a contract — Honor it
  • Deprecate before removing — Give consumers time
  • Automate everything — Version bumps, changelogs, releases
  • Document clearly — Migration guides, compatibility matrices

Your SDK's versioning strategy reflects your respect for consumers' time.


Versioning strategies refined through years of maintaining SDKs consumed by hundreds of applications.

Abraham Jeyaraj

Written by Abraham Jeyaraj

AI-Powered Solutions Architect with 20+ years of experience in enterprise software development.