A Kotlin Multiplatform library for decimal number formatting with automatic input formatting and Jetpack Compose UI components.
Perfect for financial apps, calculators, and any application that needs professional decimal number input and display.
- π Kotlin Multiplatform - Works on Android, iOS, and more
- π¨ Jetpack Compose - Ready-to-use UI components
- π° Currency Support - Built-in prefix support for currency symbols
- π International Formats - European, US, Swiss, and custom formats
- β‘ Real-time Formatting - Automatic formatting as users type
- π§© Modular Design - Use core logic only or with UI components
- π§ Highly Configurable - Decimal places, separators, max digits
- π Structured Data - Get raw digits, formatted display, and full formatted display
dependencies {
implementation("dev.robercoding:decimal-formatter-core:0.0.1-alpha01")
}
dependencies {
implementation("dev.robercoding:decimal-formatter-compose:0.0.1-alpha01")
// Note: This automatically includes the core module
}
// Create a formatter
val formatter = DecimalFormatter(DecimalFormatterConfiguration.european())
// Format numbers
val formatted = formatter.format("123456") // "1.234,56"
The new API uses UiDecimalFormatter
for better encapsulation and DecimalValue
for structured state management:
@Composable
fun CurrencyInput() {
// Create a UI formatter with prefix
val europeanFormatter = rememberUiDecimalFormatter(configuration = DecimalFormatterConfiguration.european(), prefix = "β¬")
// Use DecimalValue for structured state
var price by remember {
mutableStateOf(europeanFormatter.format("")) // Start with empty if needed, will format to "β¬0,00"
}
OutlinedDecimalTextField(
decimalValue = price,
onValueChange = { price = it },
decimalFormatter = europeanFormatter,
label = { Text("Price") }
)
}
@Composable
fun WeightInput() {
// Custom configuration without prefix
val decimalFormatter = remember {
UiDecimalFormatter(
decimalFormatter = DecimalFormatter(
DecimalFormatterConfiguration(
decimalSeparator = DecimalSeparator.DOT,
thousandSeparator = ThousandSeparator.COMMA,
decimalPlaces = 3,
maxDigits = 10
)
),
prefix = null
)
}
var weight by remember {
mutableStateOf(decimalFormatter.format("123456")) // Initialize with value, will format to "123,456"
}
OutlinedDecimalTextField(
decimalValue = weight,
onValueChange = { weight = it },
decimalFormatter = decimalFormatter,
label = { Text("Kilograms") }
)
}
@Composable
fun DynamicFormatterExample() {
var isEuropean by remember { mutableStateOf(false) }
val usFormatter = rememberUiDecimalFormatter(DecimalFormatterConfiguration.us(), prefix = "$")
val europeanFormatter = rememberUiDecimalFormatter(DecimalFormatterConfiguration.european(), prefix = "β¬")
var amount by remember { mutableStateOf(usFormatter.format("123456")) }
Column {
OutlinedDecimalTextField(
decimalValue = amount,
onValueChange = { amount = it },
decimalFormatter = if (isEuropean) europeanFormatter else usFormatter
)
Button(
onClick = {
isEuropean = !isEuropean
// Reformat existing data with new formatter
val newFormatter = if (isEuropean) europeanFormatter else usFormatter
amount = newFormatter.format(amount.rawDigits)
}
) {
Text("Switch Format")
}
}
}
Component | Description |
---|---|
DecimalFormatter |
Core formatting logic |
DecimalFormatterConfiguration |
Formatting configuration |
DecimalSeparator / ThousandSeparator |
Separator enums |
Component | Description |
---|---|
OutlinedDecimalTextField |
Material 3 outlined text field |
UiDecimalFormatter |
UI-layer formatter with prefix support |
DecimalValue |
Structured data class for formatted values |
The DecimalValue
data class provides structured access to formatted data:
data class DecimalValue internal constructor(
val rawDigits: String, // "123456" - pure digits for any calculation you need
val display: String, // "1.234,56" - formatted without prefix
val fullDisplay: String? // "β¬1.234,56" - formatted with prefix. Nullable if you didn't provide a prefix
)
// Note: You can only create `DecimalValue` using `UiDecimalFormatter.format()`
// Usage examples
val value = uiDecimalFormatter.format("123456")
println(value.rawDigits) // "123456"
println(value.display) // "1.234,56" -> Can vary depending on the type of formatter used
println(value.fullDisplay) // "β¬1.234,56" -> Can vary depending on the type of formatter used.
// Use display for showing to users
Text(value.fullDisplay ?: value.display)
// European: 1.234,56
DecimalFormatterConfiguration.european()
// US: 1,234.56
DecimalFormatterConfiguration.us()
// Swiss: 1'234.56
DecimalFormatterConfiguration.swiss()
// Plain: 1234.56 (no thousand separators)
DecimalFormatterConfiguration.plain()
Any configuration can be customized using DecimalFormatterConfiguration
:
DecimalFormatterConfiguration(
decimalSeparator = DecimalSeparator.COMMA,
thousandSeparator = ThousandSeparator.SPACE,
decimalPlaces = 3,
maxDigits = 10
)
// The formatter automatically handles invalid input
formatter.format("abc123def") // Filters the letters
formatter.format("$1,234.56") // Extracts digits β "123456" β "1,234.56"
formatter.format("000123") // Removes leading zeros β "123" β "1.23"
Enable debug logging to troubleshoot formatting issues:
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
DecimalFormatterDebugConfig.setDebugEnabled(true)
}
}
This library is split into two modules:
decimal-formatter-core
- Pure Kotlin logic, works everywheredecimal-formatter-compose
- Jetpack Compose UI components
decimal-formatter/
βββ core/ # Platform-agnostic formatting
βββ formatter/
βββ DecimalFormatter # Core formatting logic
βββ DecimalFormatterConfiguration # Formatting rules
βββ DecimalFormatterDebugConfig # Debug logging configuration
βββ model/
βββ ThousandSeparator # Enum for thousand separators
βββ DecimalSeparator # Enum for decimal separators
βββ utils/
βββ LoggerUtils # Utility for logging
βββ compose/ # Compose UI components
βββ components/
βββ DecimalTextField # Basic text field without any decorations
βββ OutlinedDecimalTextField # Material text field
βββ model/
βββ DecimalValue
βββ formatter/
βββ UiDecimalFormatter # UI-layer formatter with prefix support
Contributions are welcome!
Copyright 2025 Roberto Fuentes
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.
Made with β€οΈ by Roberto Fuentes