Hi everyone :wave: I recently open-sourced a Kotl...
# feed
a
Hi everyone 👋 I recently open-sourced a Kotlin Multiplatform library called
phonenumber
for phone number validation and formatting across Android and iOS. Why I built it: While working on a KMP app, I found myself duplicating phone number validation logic across Android and iOS. Android had
libphonenumber
, iOS had
PhoneNumberKit
, and keeping behavior consistent between both platforms became messy. So I built a shared KMP wrapper that exposes a single API while delegating to each platform’s native implementation. Features: - Global phone number validation - Validation by country ISO code - Validation by country calling code - Format to E.164 - Format to international format - Extract region code - Extract country calling code - Shared API for Android & iOS - Native performance (
libphonenumber
on Android,
PhoneNumberKit
on iOS) GitHub: https://github.com/ajayinnovations/phonenumber Would love feedback on the API design, naming, and any edge cases I might have missed 🙏
✅ 1
👍 3
c
Feedback: you marked
PhoneNumberInfo
as a
data class
but it's likely that you will add new fields in the future. This is not recommended, see why → https://jakewharton.com/public-api-challenges-in-kotlin/
Additionally, this allows users to write:
Copy code
val info = PhoneNumberValidator.parse("+2348012345678")
    .copy(international = "whatever ahah")
which makes no sense.
If two phone numbers are the same but formatted differently, it probably makes more sense that
==
reports them identical, but your existing implementation would report that they are different, so a
Set<PhoneNumberInfo>
would not work if the input format is different. To solve all of these problems, you can structure it as:
Copy code
public class PhoneNumberInfo internal constructor(
    val international: String?,
) {
    val e164 = PhoneNumberValidator.format(international, PhoneNumberFormat.E164)

    val regionCode = PhoneNumberValidator.regionCode(international)
    
    val countryCode = PhoneNumberValidator.countryCode(international)

    override fun toString() = "PhoneNumberInfo($international)"

    override fun equals(other: Any?): Boolean {
        return other is PhoneNumberInfo && international == other.international
    }

    override fun hashCode(): Int = international.hashCode()
}

// Fake constructor for convenience
fun PhoneNumberInfo(
    input: String,
) : PhoneNumberInfo(PhoneNumberValidator.format(input, PhoneNumberFormat.INTERNATIONAL))
You may add
by lazy
on some fields if they are expensive to compute. At this point, you may call this type
PhoneNumber
instead of
PhoneNumberInfo
, since it contains all info for a phone number.
And then if you want to push it further, all methods of
PhoneNumberValidator
could be moved into
PhoneNumber
's
companion object
, but it objectively doesn't change much, it's a matter of taste and your preference. The upside is that the entire library can fit in one less public type.
a
Hello, I really appreciate your feedback and I will implement it in the upcoming version coming out in a few days 🙏
c
If you want, you can send me the PR in DM and I'll review it