Prince of Versions KMP is a Kotlin Multiplatform library that handles app update checks by fetching update configurations. It returns update results with version information, status, and metadata to help you implement update flows across Android, JVM and iOS platforms. The library offers extensive customization options for update info fetching to fit your specific needs.
- Getting started
- Usage
- Requirements
- Contributing
- License
- Credits
Add the dependency to your project from Maven Central:
// In your shared module's build.gradle.kts
kotlin {
sourceSets {
commonMain.dependencies {
implementation("com.infinum:kmp-prince-of-versions:0.1.0")
}
}
}or if you want to use the library in only a subset of platform modules:
kotlin {
sourceSets {
androidMain.dependencies {
implementation("com.infinum:kmp-prince-of-versions:0.1.0")
}
jvmMain.dependencies {
implementation("com.infinum:kmp-prince-of-versions:0.1.0")
}
iosMain.dependencies {
// e.g. iOS uses a different solution for detecting updates
}
}
}// In your app's build.gradle.kts
dependencies {
implementation("com.infinum:kmp-prince-of-versions:0.1.0")
}// In your build.gradle.kts
dependencies {
implementation("com.infinum:kmp-prince-of-versions:0.1.0")
}For iOS projects, you can integrate the library using Swift Package Manager:
// In your Package.swift
dependencies: [
.package(url: "https://github.com/infinum/kmp-prince-of-versions.git", from: "0.1.0")
]Or add it directly in Xcode:
- File → Add Package Dependencies
- Enter repository URL:
https://github.com/infinum/kmp-prince-of-versions.git - Select version
0.1.0or later
Download the prebuilt XCFramework from Releases and add it to your Xcode project.
The library provides platform-specific factory methods to create PrinceOfVersions instances. Each platform has its own requirements and initialization patterns.
Note: You'll need to create an API endpoint on your server where the remote update configuration JSON will be hosted and made available for the library to fetch. If you need a more specific setup consult Advanced Usage with Custom Components
For complete working examples, see the sample apps: Android/JVM sample app and iOS sample app which demonstrate usage across all platforms. Additional examples and edge cases can be found in the test suites: common tests, Android tests, and iOS tests.
import com.infinum.princeofversions.PrinceOfVersions
import com.infinum.princeofversions.UpdateStatus
// Simple initialization with default components
val princeOfVersions = PrinceOfVersions(context)
// Check for updates from a URL
val result = princeOfVersions.checkForUpdatesFromUrl("https://your-server.com/update-config.json")
when (result.status) {
UpdateStatus.MANDATORY -> {
// Handle required update
showUpdateDialog(result.version, result.metadata)
}
UpdateStatus.OPTIONAL -> {
// Handle optional update
showOptionalUpdateDialog(result.version, result.metadata)
}
UpdateStatus.NO_UPDATE -> {
// App is up to date
}
}In iOS, you'll need to create the instance in your iOS-specific code:
// In your iOS module
import PrinceOfVersions
let princeOfVersions = IosPrinceOfVersionsKt.createPrinceOfVersions()
// Use with async/await
// Note: Kotlin extension functions are exposed as static methods in Swift
// that take the instance as the first parameter
Task {
do {
let result = try await IosPrinceOfVersionsKt.checkForUpdatesFromUrl(
princeOfVersions, // Instance passed as first parameter
url: "https://your-server.com/update-config.json",
username: nil,
password: nil,
networkTimeout: 60_000 // milliseconds
)
switch result.status {
case .mandatory:
// Mandatory update - user must update to continue
// Implement your own logic to show a blocking dialog
print("Mandatory update required: \(result.version ?? "unknown")")
// Example: showForceUpdateDialog(version: result.version)
case .optional:
// Optional update - user can choose to update or dismiss
// Implement your own logic to show a dismissible notification
print("Optional update available: \(result.version ?? "unknown")")
// Example: showOptionalUpdateDialog(version: result.version)
case .noUpdate:
// App is up to date
print("App is up to date")
default:
break
}
} catch let error as RequirementsNotSatisfiedException {
// Device doesn't meet requirements (e.g., OS version)
print("Requirements not met")
// Access metadata to understand which requirements failed
if let metadata = error.metadata as? [String: String] {
print("Metadata: \(metadata)")
}
} catch let error as ConfigurationException {
// Configuration or JSON parsing error
print("Configuration error: \(error.message ?? "Unknown error")")
if let cause = error.cause {
print("Caused by: \(cause)")
}
} catch let error as IoException {
// Network error
print("Network error: \(error.message ?? "Connection failed")")
} catch {
print("Unexpected error: \(error.localizedDescription)")
}
}import com.infinum.princeofversions.PrinceOfVersions
import com.infinum.princeofversions.UpdateStatus
// Initialize with your main application class
val princeOfVersions = PrinceOfVersions(YourMainClass::class.java)
// Check for updates
val result = princeOfVersions.checkForUpdatesFromUrl("https://your-server.com/update-config.json")
when (result.status) {
UpdateStatus.MANDATORY -> {
// Handle required update
println("Required update to version ${result.version}")
}
UpdateStatus.OPTIONAL -> {
// Handle optional update
println("Optional update to version ${result.version} available")
}
UpdateStatus.NO_UPDATE -> {
println("App is up to date")
}
}You can customize the library behavior by providing your own components:
val customPrinceOfVersions = PrinceOfVersions(context) {
// Custom configuration parser
configurationParser = MyCustomConfigurationParser()
// Custom requirement checkers
withRequirementCheckers(
mapOf("custom_key" to MyCustomRequirementChecker()),
keepDefaultCheckers = true
)
// Custom storage implementation
storage = MyCustomStorage()
// Custom version provider
versionProvider = MyCustomVersionProvider()
}Instead of checkForUpdatesFromUrl, you can provide your own Loader implementation:
class MyCustomLoader : Loader {
override suspend fun load(): String {
// Your custom loading logic
return fetchConfigurationFromCustomSource()
}
}
val result = princeOfVersions.checkForUpdates(MyCustomLoader())In KMP projects, the recommended approach is to create platform-specific instances in platform modules, then define your business logic in commonMain that works with the base interface directly. This way you avoid duplicating business logic across platforms. Since the platforms use varying types for their versions, generics have to be used.
// Business logic use case in commonMain using the base interface directly
class YourUseCase<T>(
private val princeOfVersions: PrinceOfVersionsBase<T>
) {
...
suspend fun checkForUpdates(source: Loader): BaseUpdateResult<T> {
return princeOfVersions.checkForUpdates(source)
}
suspend fun checkForUpdatesFromUrl(
...
): BaseUpdateResult<T> {
return princeOfVersions.checkForUpdatesFromUrl(...)
}
suspend fun handleUpdateResult(...) {
val result = checkForUpdates(source)
when (result.status) {
UpdateStatus.MANDATORY -> ...
UpdateStatus.OPTIONAL -> ...
UpdateStatus.NO_UPDATE -> ...
}
}
...
}// Use case creation function
fun createAndroidUseCase(context: Context): YourUseCase<Long> {
val princeOfVersions = PrinceOfVersions(context)
return CheckForUpdatesUseCase(princeOfVersions)
}// Use case creation function
fun createIosUseCase(): YourUseCase<String> {
val princeOfVersions = createPrinceOfVersions()
return CheckForUpdatesUseCase(princeOfVersions)
}// Use case creation function
fun createJvmUseCase(mainClass: Class<*>): YourUseCase<String> {
val princeOfVersions = PrinceOfVersions(mainClass)
return YourUseCase(princeOfVersions)
}The library expects a JSON configuration that contains platform-specific update information. The structure varies slightly between platforms but follows similar patterns.
{
"android": { /* Android configuration */ },
"android2": { /* Alternative Android configuration */ },
"jvm": { /* JVM/Desktop configuration */ },
"ios": { /* iOS configuration (legacy format) */ },
"ios2": { /* iOS configuration (recommended) */ },
"meta": {
"title": "New Update Available",
"description": "This update includes bug fixes and performance improvements."
}
}android2- Primary Android configuration key (recommended)android- Fallback Android configuration key (for backward compatibility)jvm- JVM/Desktop configuration keyios2- Primary iOS configuration key (recommended)ios- Fallback iOS configuration key (for backward compatibility)
Each platform configuration can contain the following properties (all are optional, but at least one version property should be provided):
required_version- The minimum version required for the app to function- Android: Long value (version code)
- iOS: String value (semantic version like "1.2.0")
- JVM: String value (semantic version like "1.2.0")
last_version_available- The latest available version for optional updates- Android: Long value (version code)
- iOS: String value (semantic version like "1.2.0")
- JVM: String value (semantic version like "1.2.0")
notify_last_version_frequency- How often to notify about optional updates- Values:
"ONCE"(default) or"ALWAYS"
- Values:
requirements- Object containing update requirements that must be satisfiedmeta- Object containing metadata specific to this update configuration
- Required Update: If the current version is below
required_version, a mandatory update is triggered. If the current version is belowrequired_version, and thelast_version_availablevalue is also provided, a mandatory update is triggered, with the version value being set to whichever one is greater. - Optional Update: If the current version is below
last_version_availablebut meetsrequired_version, an optional update is available - No Update: If the current version meets both requirements, no update is needed
The requirements object can contain various conditions that must be met for the update to be applicable:
{
"requirements": {
"required_os_version": "21",
"required_jvm_version": "11",
"custom_requirement": "some_value"
}
}Default Requirements:
required_os_version- Minimum OS version required- Android: API level (e.g., "21" for Android 5.0)
- iOS: iOS version (e.g., "14.0" for iOS 14)
required_jvm_version- Minimum JVM version required (JVM only)- JVM: JVM version number (e.g., "8", "11", "17")
Custom Requirements: You can add custom requirements and handle them with a custom RequirementChecker implementation.
Platforms can use arrays to provide multiple configuration options. The library processes them in order and selects the first one whose requirements are satisfied:
{
"android2": [
{
"required_version": 10,
"last_version_available": 12,
"requirements": {
"required_os_version": "21"
},
"meta": {
"update_type": "major"
}
},
{
"required_version": 8,
"last_version_available": 10,
"requirements": {
"required_os_version": "19"
},
"meta": {
"update_type": "minor"
}
}
]
}Metadata is merged with the following priority (highest to lowest):
- Selected configuration metadata -
metafrom the chosen update configuration - Root metadata -
metafrom the root level of the JSON
If there are conflicting keys, the selected configuration metadata takes precedence.
Android Configuration:
{
"android2": {
"required_version": 15,
"last_version_available": 18,
"notify_last_version_frequency": "ONCE",
"requirements": {
"required_os_version": "21"
},
"meta": {
"changelog_url": "https://example.com/changelog",
"download_url": "https://play.google.com/store/apps/details?id=com.example.app"
}
},
"meta": {
"title": "Update Available",
"description": "Please update to the latest version"
}
}JVM Configuration:
{
"jvm": {
"required_version": "1.2.0",
"last_version_available": "1.5.0",
"notify_last_version_frequency": "ALWAYS",
"requirements": {
"required_jvm_version": "11"
},
"meta": {
"download_url": "https://example.com/download/app-1.5.0.jar"
}
},
"meta": {
"title": "New Version Available",
"description": "Enhanced performance and new features"
}
}iOS Configuration:
{
"ios2": {
"required_version": "1.2.0",
"last_version_available": "1.5.0",
"notify_last_version_frequency": "ONCE",
"requirements": {
"required_os_version": "14.0"
},
"meta": {
"release_notes_url": "https://example.com/release-notes"
}
},
"meta": {
"title": "Update Available",
"description": "Bug fixes and performance improvements"
}
}Multi-Platform Configuration:
{
"android2": {
"required_version": 15,
"last_version_available": 18,
"notify_last_version_frequency": "ONCE"
},
"ios2": {
"required_version": "1.2.0",
"last_version_available": "1.5.0",
"notify_last_version_frequency": "ONCE"
},
"jvm": {
"required_version": "1.2.0",
"last_version_available": "1.5.0",
"notify_last_version_frequency": "ALWAYS"
},
"meta": {
"title": "Update Available",
"description": "Bug fixes and improvements",
"release_notes": "https://example.com/release-notes"
}
}To support both legacy and current versions of Prince of Versions:
- Use
android2for new Android implementations - Use
ios2for new iOS implementations - Keep
androidandiosas fallbacks for older versions - The library will prefer
android2overandroidandios2overiosif both are present
- If no platform key is found, the parser will throw an error
- If requirements are not satisfied for any configuration in an array, a
RequirementsNotSatisfiedExceptionis thrown - Invalid version formats or missing required fields will result in parsing exceptions
- On iOS, configuration errors are wrapped in
ConfigurationExceptionfor Swift interop - Network errors are thrown as
IoExceptionacross all platforms
The library requires the following tool versions:
- Android: Minimum SDK level 24
- iOS: iOS 14.0+ / Xcode 15.0+
- JVM: Java version 17+
We believe that the community can help us improve and build better a product. Please refer to our contributing guide to learn about the types of contributions we accept and the process for submitting them.
To ensure that our community remains respectful and professional, we defined a code of conduct that we expect all contributors to follow.
We appreciate your interest and look forward to your contributions.
Copyright 2026 Infinum
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Maintained and sponsored by Infinum.