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 Type | Breaking? | Version Bump |
|---|---|---|
| Remove public method | Yes | MAJOR |
| Change method signature | Yes | MAJOR |
| Change default behavior | Yes | MAJOR |
| Add required parameter | Yes | MAJOR |
| Add optional parameter | No | MINOR |
| Add new method | No | MINOR |
| Add new class | No | MINOR |
| Fix bug (same behavior contract) | No | PATCH |
| Performance improvement | No | PATCH |
| Documentation update | No | PATCH |
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 Version | iOS | Android | Xcode | Kotlin |
|---|---|---|---|---|
| 2.0.x | 15.0+ | API 26+ | 15.0+ | 1.9+ |
| 1.5.x | 14.0+ | API 24+ | 14.0+ | 1.8+ |
| 1.4.x | 13.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.
