
Performs remote app-update configuration fetching and resolves update status, version, and metadata; offers customizable configuration parsing, requirements checking, loaders, storage, and version providers.
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.
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:
https://github.com/infinum/kmp-prince-of-versions.git
0.1.0 or laterDownload 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
last_version_available - The latest available version for optional updates
notify_last_version_frequency - How often to notify about optional updates
"ONCE" (default) or "ALWAYS"
requirements - Object containing update requirements that must be satisfiedmeta - Object containing metadata specific to this update configurationrequired_version, a mandatory update is triggered. If the current version is below required_version, and the last_version_available value is also provided, a mandatory update is triggered, with the version value being set to whichever one is greater.last_version_available but meets required_version, an optional update is availableThe 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
required_jvm_version - Minimum JVM version required (JVM only)
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):
meta from the chosen update configurationmeta from the root level of the JSONIf 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:
android2 for new Android implementationsios2 for new iOS implementationsandroid and ios as fallbacks for older versionsandroid2 over android and ios2 over ios if both are presentRequirementsNotSatisfiedException is thrownConfigurationException for Swift interopIoException across all platformsThe library requires the following tool versions:
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.
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.
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:
https://github.com/infinum/kmp-prince-of-versions.git
0.1.0 or laterDownload 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
last_version_available - The latest available version for optional updates
notify_last_version_frequency - How often to notify about optional updates
"ONCE" (default) or "ALWAYS"
requirements - Object containing update requirements that must be satisfiedmeta - Object containing metadata specific to this update configurationrequired_version, a mandatory update is triggered. If the current version is below required_version, and the last_version_available value is also provided, a mandatory update is triggered, with the version value being set to whichever one is greater.last_version_available but meets required_version, an optional update is availableThe 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
required_jvm_version - Minimum JVM version required (JVM only)
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):
meta from the chosen update configurationmeta from the root level of the JSONIf 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:
android2 for new Android implementationsios2 for new iOS implementationsandroid and ios as fallbacks for older versionsandroid2 over android and ios2 over ios if both are presentRequirementsNotSatisfiedException is thrownConfigurationException for Swift interopIoException across all platformsThe library requires the following tool versions:
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.