pale-blue-kmp-core

Enables shared logic across platforms with networking capabilities using Ktor and cross-platform key-value storage utilizing datastore and multiplatform-settings. Seamlessly handles requests and data storage.

Android JVMKotlin/NativeWasm
GitHub stars36
Open issues0
LicenseApache License 2.0
Creation dateabout 1 year ago

Last activityabout 1 month ago
Latest release2.0.1 (about 1 month ago)

Pale Blue KMP Core

A Kotlin Multiplatform (KMP) library for shared logic and common utilities across platforms (Android, iOS)

Maven Central

Features

  • Networking: A convenient layer for making network requests based on Ktor.
  • Key-Value Storage: A cross-platform solution for storing and retrieving key-value pairs based on datastore and multiplatform-settings.
  • KmmResult: A type-safe wrapper for handling success and failure cases, inspired by Kotlin Result, with full type information preserved on all platforms.
  • Currency Formatter: A utility for formatting monetary values based on locale and currency code.
  • Number Formatter: A utility for formatting numbers based on locale.

Supported Platforms

Feature Android iOS WasmJs
Networking (ApiManager)
Key-Value Storage (PreferencesManager)
Key-Value Storage (EncryptedPreferencesManager)
KmmResult
Currency Formatter
Number Formatter
Rating Service

Getting Started

Add the library dependency to your build.gradle.kts or build.gradle file.

dependencies {
    implementation("com.paleblueapps:kmpcore:2.0.0")
}

Migration to 2.0.0

Version 2.0.0 introduces a breaking change to key-value storage: encrypted and non-encrypted storage are now separate managers.

  • PreferencesManager now handles only non-encrypted storage
  • EncryptedPreferencesManager now handles encrypted storage
  • Shared APIs moved to BasePreferencesManager

See MIGRATION.md for the full upgrade guide.

Networking example

// Create ApiManager
val apiManager = ApiManager(
    baseUrl = "https://api.example.com/",
    enableLogging = true,
    defaultRequestConfig = {
        header("X-ID-device", "android")
        header("X-API-Version", "1")
    },
    responseValidator = { response ->
        when (response.status.value) {
            in 200..299 -> {}
            401 -> throw Error.UnauthorizedError
            in 400..499 -> throw Error.BackendResponseError
            else -> throw Error.BackendError
        }
    },
) // Or you can pass the custom Ktor client instance



// Usage
val endpoint = Endpoint("/users", HttpMethod.Get)
val userResult: Result<User> = apiManager.call(endpoint)
userResult.onSuccess { user ->
    println("User ID: ${user.id}, User Name: ${user.name}")
}.onFailure { exception ->
    println("Error: ${exception.message}")
}

Key-value storage example

// file: commonMain/PreferencesManager.kt
val preferencesFileName = "preferences.preferences_pb" // NOTE: this file extension should be .preferences_pb
val encryptedPreferencesFileName = "encrypted_preferences"

expect val preferencesManager: PreferencesManager
expect val encryptedPreferencesManager: EncryptedPreferencesManager

// file: androidMain/PreferencesManager.kt
actual val preferencesManager = PreferencesManager(
    context = androidContext(), // pass the Android context here either manually or using a DI framework
    fileName = preferencesFileName,
)

actual val encryptedPreferencesManager = EncryptedPreferencesManager(
    context = androidContext(),
    fileName = encryptedPreferencesFileName,
)

// file: iosMain/PreferencesManager.kt
actual val preferencesManager = PreferencesManager(
    fileName = preferencesFileName,
)

actual val encryptedPreferencesManager = EncryptedPreferencesManager(
    fileName = encryptedPreferencesFileName,
)

// file: wasmJsMain/PreferencesManager.kt
actual val preferencesManager = PreferencesManager()
// Note: EncryptedPreferencesManager is not available on wasmJs.

// Encrypted storage example
encryptedPreferencesManager.putString("user_token", token)
val userToken = encryptedPreferencesManager.getString("user_token")

// Non-encrypted storage example
preferencesManager.putString("user_id", userId)
val storedUserId = preferencesManager.getString("user_id")

On wasmJs, PreferencesManager uses browser-backed storage and does not require a fileName. EncryptedPreferencesManager is not supported on wasmJs.

Currency formatter example

// Create CurrencyFormatter
val currencyFormatter = CurrencyFormatter()

// Usage
val formattedAmount = currencyFormatter.format(
    amount = 1234.56,
    currencyCode = "USD",
    withCurrencySymbol = true,
    minimumFractionDigits = 2,
    maximumFractionDigits = 2
) // "$1,234.56"

Number formatter example

// Create NumberFormatter
val numberFormatter = NumberFormatter()

// Usage
val formattedNumber = numberFormatter.format(
    number = 1234567.89,
    localeCode = "en-US",
) // "1,234,567.89"

Auto rating system

The RatingService helps automate the process of asking users for app ratings or feedback. It tracks user actions and ensures that prompts are not shown too frequently.

1. Setup

In your common code, you can define the RatingService instance.

// commonMain
expect val ratingService: RatingService

On Android, provide the applicationContext:

// androidMain
actual val ratingService: RatingService = RatingService(applicationContext)

// In your Activity, bind it to handle dialog presentation
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ratingService.bind(this)
    }
}

On iOS:

// iosMain
actual val ratingService: RatingService = RatingService()

2. Configuration

Configure the service with custom dialog messages and rules:

ratingService.configure(
    ratingDialogConfig = DialogConfig(
        title = "Enjoying the app?",
        message = "Would you like to rate us on the store?",
        positiveButtonText = "Rate Now",
        negativeButtonText = "Maybe Later"
    ),
    feedbackDialogConfig = DialogConfig(
        title = "Feedback",
        message = "How can we improve?",
        positiveButtonText = "Send Feedback",
        negativeButtonText = "Cancel"
    ),
    snoozeDuration = 30.days,
    minActionsNeededForAskingReview = 5
)

3. Tracking and Triggering

Log user actions (e.g., when a user completes a task) and attempt to show the rating flow:

// Log an action
ratingService.logUserAction()

// Start the rating flow
ratingService.startRatingFlow { event ->
    when (event) {
        RatingEvent.OnRatingPositiveClick -> {
            // Redirect to App Store / Play Store
        }
        RatingEvent.OnFeedbackPositiveClick -> {
            // Open feedback form or email
        }
        else -> {
            // Handle other events if needed
        }
    }
}
Android JVMKotlin/NativeWasm
GitHub stars36
Open issues0
LicenseApache License 2.0
Creation dateabout 1 year ago

Last activityabout 1 month ago
Latest release2.0.1 (about 1 month ago)

Pale Blue KMP Core

A Kotlin Multiplatform (KMP) library for shared logic and common utilities across platforms (Android, iOS)

Maven Central

Features

  • Networking: A convenient layer for making network requests based on Ktor.
  • Key-Value Storage: A cross-platform solution for storing and retrieving key-value pairs based on datastore and multiplatform-settings.
  • KmmResult: A type-safe wrapper for handling success and failure cases, inspired by Kotlin Result, with full type information preserved on all platforms.
  • Currency Formatter: A utility for formatting monetary values based on locale and currency code.
  • Number Formatter: A utility for formatting numbers based on locale.

Supported Platforms

Feature Android iOS WasmJs
Networking (ApiManager)
Key-Value Storage (PreferencesManager)
Key-Value Storage (EncryptedPreferencesManager)
KmmResult
Currency Formatter
Number Formatter
Rating Service

Getting Started

Add the library dependency to your build.gradle.kts or build.gradle file.

dependencies {
    implementation("com.paleblueapps:kmpcore:2.0.0")
}

Migration to 2.0.0

Version 2.0.0 introduces a breaking change to key-value storage: encrypted and non-encrypted storage are now separate managers.

  • PreferencesManager now handles only non-encrypted storage
  • EncryptedPreferencesManager now handles encrypted storage
  • Shared APIs moved to BasePreferencesManager

See MIGRATION.md for the full upgrade guide.

Networking example

// Create ApiManager
val apiManager = ApiManager(
    baseUrl = "https://api.example.com/",
    enableLogging = true,
    defaultRequestConfig = {
        header("X-ID-device", "android")
        header("X-API-Version", "1")
    },
    responseValidator = { response ->
        when (response.status.value) {
            in 200..299 -> {}
            401 -> throw Error.UnauthorizedError
            in 400..499 -> throw Error.BackendResponseError
            else -> throw Error.BackendError
        }
    },
) // Or you can pass the custom Ktor client instance



// Usage
val endpoint = Endpoint("/users", HttpMethod.Get)
val userResult: Result<User> = apiManager.call(endpoint)
userResult.onSuccess { user ->
    println("User ID: ${user.id}, User Name: ${user.name}")
}.onFailure { exception ->
    println("Error: ${exception.message}")
}

Key-value storage example

// file: commonMain/PreferencesManager.kt
val preferencesFileName = "preferences.preferences_pb" // NOTE: this file extension should be .preferences_pb
val encryptedPreferencesFileName = "encrypted_preferences"

expect val preferencesManager: PreferencesManager
expect val encryptedPreferencesManager: EncryptedPreferencesManager

// file: androidMain/PreferencesManager.kt
actual val preferencesManager = PreferencesManager(
    context = androidContext(), // pass the Android context here either manually or using a DI framework
    fileName = preferencesFileName,
)

actual val encryptedPreferencesManager = EncryptedPreferencesManager(
    context = androidContext(),
    fileName = encryptedPreferencesFileName,
)

// file: iosMain/PreferencesManager.kt
actual val preferencesManager = PreferencesManager(
    fileName = preferencesFileName,
)

actual val encryptedPreferencesManager = EncryptedPreferencesManager(
    fileName = encryptedPreferencesFileName,
)

// file: wasmJsMain/PreferencesManager.kt
actual val preferencesManager = PreferencesManager()
// Note: EncryptedPreferencesManager is not available on wasmJs.

// Encrypted storage example
encryptedPreferencesManager.putString("user_token", token)
val userToken = encryptedPreferencesManager.getString("user_token")

// Non-encrypted storage example
preferencesManager.putString("user_id", userId)
val storedUserId = preferencesManager.getString("user_id")

On wasmJs, PreferencesManager uses browser-backed storage and does not require a fileName. EncryptedPreferencesManager is not supported on wasmJs.

Currency formatter example

// Create CurrencyFormatter
val currencyFormatter = CurrencyFormatter()

// Usage
val formattedAmount = currencyFormatter.format(
    amount = 1234.56,
    currencyCode = "USD",
    withCurrencySymbol = true,
    minimumFractionDigits = 2,
    maximumFractionDigits = 2
) // "$1,234.56"

Number formatter example

// Create NumberFormatter
val numberFormatter = NumberFormatter()

// Usage
val formattedNumber = numberFormatter.format(
    number = 1234567.89,
    localeCode = "en-US",
) // "1,234,567.89"

Auto rating system

The RatingService helps automate the process of asking users for app ratings or feedback. It tracks user actions and ensures that prompts are not shown too frequently.

1. Setup

In your common code, you can define the RatingService instance.

// commonMain
expect val ratingService: RatingService

On Android, provide the applicationContext:

// androidMain
actual val ratingService: RatingService = RatingService(applicationContext)

// In your Activity, bind it to handle dialog presentation
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ratingService.bind(this)
    }
}

On iOS:

// iosMain
actual val ratingService: RatingService = RatingService()

2. Configuration

Configure the service with custom dialog messages and rules:

ratingService.configure(
    ratingDialogConfig = DialogConfig(
        title = "Enjoying the app?",
        message = "Would you like to rate us on the store?",
        positiveButtonText = "Rate Now",
        negativeButtonText = "Maybe Later"
    ),
    feedbackDialogConfig = DialogConfig(
        title = "Feedback",
        message = "How can we improve?",
        positiveButtonText = "Send Feedback",
        negativeButtonText = "Cancel"
    ),
    snoozeDuration = 30.days,
    minActionsNeededForAskingReview = 5
)

3. Tracking and Triggering

Log user actions (e.g., when a user completes a task) and attempt to show the rating flow:

// Log an action
ratingService.logUserAction()

// Start the rating flow
ratingService.startRatingFlow { event ->
    when (event) {
        RatingEvent.OnRatingPositiveClick -> {
            // Redirect to App Store / Play Store
        }
        RatingEvent.OnFeedbackPositiveClick -> {
            // Open feedback form or email
        }
        else -> {
            // Handle other events if needed
        }
    }
}