Skip to content

Commit e5c73c5

Browse files
committed
chore(deps): drop Google's libphonenumber, use the Android port everywhere
The app depended on two libphonenumber artifacts: the Android port (io.michaelrocks:libphonenumber-android) and Google's desktop artifact (com.googlecode.libphonenumber:libphonenumber). Each ships its own copy of the metadata, so any version skew between them meant the two could disagree on whether a number is valid — and since the port lags upstream, it always set the ceiling and blocked routine bumps of the Google artifact. Three of the four declarations were dead: :services:flipcash, :services:flipcash-compose and :services:opencode declared the Google artifact but had no Kotlin imports and no references to i18n/phonenumbers in their compiled output. Removed. The one real consumer, PhoneRegion in :shared:onramp:coinbase, is ported onto the Android artifact. That artifact loads its metadata from the AAR's assets rather than exposing a static instance, so it needs a Context — regionFromPhone becomes an injectable PhoneRegionResolver, mirroring PhoneUtils in :shared:phone (including the lazy PhoneNumberUtil so the metadata blob is parsed on first use rather than during construction on the main thread). The test moves to Robolectric for the Context. Its 13 cases and their expected values are unchanged on purpose: they are the parity check that the port agrees with Google's artifact on every number covered. With the Google artifact gone, the dependabot group rule that kept the two aliases in lockstep is no longer needed.
1 parent 1d6dd86 commit e5c73c5

8 files changed

Lines changed: 81 additions & 69 deletions

File tree

‎.github/dependabot.yml‎

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,6 @@ updates:
55
schedule:
66
interval: weekly
77
open-pull-requests-limit: 10
8-
groups:
9-
# Both libphonenumber artifacts must move together — see the note in gradle/libs.versions.toml.
10-
# Grouping them into one PR makes any version skew visible in a single diff rather than in two
11-
# PRs that can land independently.
12-
libphonenumber:
13-
patterns:
14-
- "*libphonenumber*"
15-
ignore:
16-
# Google's libphonenumber can only move when the Android port (io.michaelrocks) has a matching
17-
# release — the two ship separate copies of the metadata and must stay on the same version, or
18-
# they can disagree on whether a number is valid. See the note in gradle/libs.versions.toml.
19-
# The port has no 9.0.37, so this specific version is held. Grouping alone does not prevent it:
20-
# a group PR still gets opened with just the one artifact when only that artifact has an update,
21-
# and closing such a PR does NOT suppress the version (Dependabot says so explicitly on close).
22-
# Remove this entry once the port publishes 9.0.37 or later.
23-
- dependency-name: "com.googlecode.libphonenumber:libphonenumber"
24-
versions: ["9.0.37"]
258
labels:
269
- "dependencies"
2710
- "type: build"

‎apps/flipcash/shared/onramp/coinbase/build.gradle.kts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ dependencies {
1818
implementation(libs.androidx.localbroadcastmanager)
1919
implementation(libs.bundles.kotlinx.serialization)
2020

21-
implementation(libs.lib.phone.number.google)
21+
implementation(libs.lib.phone.number.port)
2222
implementation(libs.play.services.wallet)
2323
implementation(libs.kotlinx.coroutines.play.services)
2424
implementation(project(":libs:messaging"))
Lines changed: 44 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
package com.flipcash.app.onramp
22

3-
import com.google.i18n.phonenumbers.PhoneNumberUtil
4-
import com.google.i18n.phonenumbers.NumberParseException
3+
import android.content.Context
4+
import dagger.hilt.android.qualifiers.ApplicationContext
5+
import io.michaelrocks.libphonenumber.android.NumberParseException
6+
import io.michaelrocks.libphonenumber.android.PhoneNumberUtil
7+
import javax.inject.Inject
8+
import javax.inject.Singleton
59

610
/**
711
* Region derived from a phone number for Coinbase buy-options lookups.
@@ -22,33 +26,48 @@ data class PhoneRegion(
2226
*/
2327
private val NYC_AREA_CODES = setOf("212", "718", "917", "646", "347", "929", "586")
2428

25-
private val phoneUtil: PhoneNumberUtil = PhoneNumberUtil.getInstance()
26-
2729
/**
28-
* Extracts the [PhoneRegion] from an E.164 phone number using libphonenumber
29-
* for country detection and area code heuristics for US subdivision.
30+
* Resolves the [PhoneRegion] for a phone number.
3031
*
31-
* @return the detected region, or null if the number can't be parsed.
32+
* Injectable rather than a top-level function because the Android libphonenumber port loads its
33+
* metadata from the AAR's assets, so it needs a [Context] — unlike Google's desktop artifact, which
34+
* exposes a static instance. See the note in `gradle/libs.versions.toml` for why the port is the
35+
* only libphonenumber this app depends on.
3236
*/
33-
fun regionFromPhone(phone: String): PhoneRegion? {
34-
val parsed = try {
35-
phoneUtil.parse(phone, null)
36-
} catch (_: NumberParseException) {
37-
return null
38-
}
37+
@Singleton
38+
class PhoneRegionResolver @Inject constructor(
39+
@ApplicationContext private val context: Context,
40+
) {
41+
// Lazy so libphonenumber's metadata blob is parsed on first *use* (a background caller), never
42+
// during construction on the main thread. Same reasoning as PhoneUtils in :shared:phone.
43+
private val phoneUtil by lazy { PhoneNumberUtil.createInstance(context) }
3944

40-
val country = phoneUtil.getRegionCodeForNumber(parsed) ?: return null
41-
val subdivision = if (country == "US") {
42-
subdivisionFromUsNumber(parsed.nationalNumber.toString())
43-
} else {
44-
null
45-
}
45+
/**
46+
* Extracts the [PhoneRegion] from an E.164 phone number using libphonenumber
47+
* for country detection and area code heuristics for US subdivision.
48+
*
49+
* @return the detected region, or null if the number can't be parsed.
50+
*/
51+
fun regionFromPhone(phone: String): PhoneRegion? {
52+
val parsed = try {
53+
phoneUtil.parse(phone, null)
54+
} catch (_: NumberParseException) {
55+
return null
56+
}
4657

47-
return PhoneRegion(country = country, subdivision = subdivision)
48-
}
58+
val country = phoneUtil.getRegionCodeForNumber(parsed) ?: return null
59+
val subdivision = if (country == "US") {
60+
subdivisionFromUsNumber(parsed.nationalNumber.toString())
61+
} else {
62+
null
63+
}
64+
65+
return PhoneRegion(country = country, subdivision = subdivision)
66+
}
4967

50-
private fun subdivisionFromUsNumber(nationalNumber: String): String? {
51-
if (nationalNumber.length < 3) return null
52-
val areaCode = nationalNumber.take(3)
53-
return if (areaCode in NYC_AREA_CODES) "NY" else null
68+
private fun subdivisionFromUsNumber(nationalNumber: String): String? {
69+
if (nationalNumber.length < 3) return null
70+
val areaCode = nationalNumber.take(3)
71+
return if (areaCode in NYC_AREA_CODES) "NY" else null
72+
}
5473
}

‎apps/flipcash/shared/onramp/coinbase/src/test/kotlin/com/flipcash/app/onramp/PhoneRegionTest.kt‎

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,58 @@
11
package com.flipcash.app.onramp
22

3+
import org.junit.runner.RunWith
4+
import org.robolectric.RobolectricTestRunner
5+
import org.robolectric.RuntimeEnvironment
6+
import org.robolectric.annotation.Config
7+
import kotlin.test.BeforeTest
38
import kotlin.test.Test
49
import kotlin.test.assertEquals
510
import kotlin.test.assertNull
611

12+
// Robolectric, because the Android libphonenumber port loads its metadata from the AAR's assets and
13+
// so needs a real Context. These cases are unchanged from when this resolved via Google's desktop
14+
// artifact — they are the parity check that the port agrees with it on every number here.
15+
@RunWith(RobolectricTestRunner::class)
16+
@Config(manifest = Config.NONE)
717
class PhoneRegionTest {
818

19+
private lateinit var resolver: PhoneRegionResolver
20+
21+
@BeforeTest
22+
fun setup() {
23+
resolver = PhoneRegionResolver(RuntimeEnvironment.getApplication())
24+
}
25+
926
// region NYC area codes
1027

1128
@Test
1229
fun `212 area code returns US-NY`() {
13-
assertEquals(PhoneRegion("US", "NY"), regionFromPhone("+12125551234"))
30+
assertEquals(PhoneRegion("US", "NY"), resolver.regionFromPhone("+12125551234"))
1431
}
1532

1633
@Test
1734
fun `718 area code returns US-NY`() {
18-
assertEquals(PhoneRegion("US", "NY"), regionFromPhone("+17185551234"))
35+
assertEquals(PhoneRegion("US", "NY"), resolver.regionFromPhone("+17185551234"))
1936
}
2037

2138
@Test
2239
fun `917 area code returns US-NY`() {
23-
assertEquals(PhoneRegion("US", "NY"), regionFromPhone("+19175551234"))
40+
assertEquals(PhoneRegion("US", "NY"), resolver.regionFromPhone("+19175551234"))
2441
}
2542

2643
@Test
2744
fun `646 area code returns US-NY`() {
28-
assertEquals(PhoneRegion("US", "NY"), regionFromPhone("+16465551234"))
45+
assertEquals(PhoneRegion("US", "NY"), resolver.regionFromPhone("+16465551234"))
2946
}
3047

3148
@Test
3249
fun `347 area code returns US-NY`() {
33-
assertEquals(PhoneRegion("US", "NY"), regionFromPhone("+13475551234"))
50+
assertEquals(PhoneRegion("US", "NY"), resolver.regionFromPhone("+13475551234"))
3451
}
3552

3653
@Test
3754
fun `929 area code returns US-NY`() {
38-
assertEquals(PhoneRegion("US", "NY"), regionFromPhone("+19295551234"))
55+
assertEquals(PhoneRegion("US", "NY"), resolver.regionFromPhone("+19295551234"))
3956
}
4057

4158
// endregion
@@ -44,7 +61,7 @@ class PhoneRegionTest {
4461

4562
@Test
4663
fun `non-NYC US area code returns US with no subdivision`() {
47-
assertEquals(PhoneRegion("US", null), regionFromPhone("+14155551234")) // SF
64+
assertEquals(PhoneRegion("US", null), resolver.regionFromPhone("+14155551234")) // SF
4865
}
4966

5067
// endregion
@@ -53,17 +70,17 @@ class PhoneRegionTest {
5370

5471
@Test
5572
fun `Canadian number returns CA`() {
56-
assertEquals(PhoneRegion("CA", null), regionFromPhone("+14165551234")) // Toronto
73+
assertEquals(PhoneRegion("CA", null), resolver.regionFromPhone("+14165551234")) // Toronto
5774
}
5875

5976
@Test
6077
fun `UK number returns GB`() {
61-
assertEquals(PhoneRegion("GB", null), regionFromPhone("+442071234567"))
78+
assertEquals(PhoneRegion("GB", null), resolver.regionFromPhone("+442071234567"))
6279
}
6380

6481
@Test
6582
fun `German number returns DE`() {
66-
assertEquals(PhoneRegion("DE", null), regionFromPhone("+4915112345678"))
83+
assertEquals(PhoneRegion("DE", null), resolver.regionFromPhone("+4915112345678"))
6784
}
6885

6986
// endregion
@@ -72,17 +89,17 @@ class PhoneRegionTest {
7289

7390
@Test
7491
fun `invalid number returns null`() {
75-
assertNull(regionFromPhone("12345"))
92+
assertNull(resolver.regionFromPhone("12345"))
7693
}
7794

7895
@Test
7996
fun `empty string returns null`() {
80-
assertNull(regionFromPhone(""))
97+
assertNull(resolver.regionFromPhone(""))
8198
}
8299

83100
@Test
84101
fun `non-numeric string returns null`() {
85-
assertNull(regionFromPhone("not-a-phone"))
102+
assertNull(resolver.regionFromPhone("not-a-phone"))
86103
}
87104

88105
// endregion

‎gradle/libs.versions.toml‎

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,13 @@ protobuf = "4.35.1"
6464
protobuf-plugin = "0.10.0"
6565
protovalidate-kt = "0.1.1"
6666

67-
# These two must stay on the SAME version. `:shared:phone` parses and formats with the Android port
68-
# while the services modules validate with Google's artifact, and each ships its own copy of the
69-
# libphonenumber metadata — so a version skew means the two can disagree on whether a number is
70-
# valid. The port lags upstream, so it sets the ceiling: only bump `-google` once the port has
71-
# published a matching release.
67+
# The Android port is the ONLY libphonenumber this app depends on, deliberately. Google's
68+
# `com.googlecode` artifact used to sit alongside it; the two ship separate copies of the metadata,
69+
# so any version skew between them meant they could disagree on whether a number is valid — and the
70+
# port lags upstream, so it always set the ceiling and blocked routine bumps.
71+
# Don't reintroduce the Google artifact. The port exposes the same API; it just needs a Context,
72+
# because it loads its metadata from the AAR's assets rather than from a static instance.
7273
lib-phone-number-port = "9.0.36"
73-
lib-phone-number-google = "9.0.36"
7474
zxing = "3.5.4"
7575

7676
androidx-benchmark-macro = "1.5.0-rc01"
@@ -251,7 +251,6 @@ vico-compose = { module = "com.patrykandpatrick.vico:compose", version.ref = "vi
251251

252252
# Phone number
253253
lib-phone-number-port = { module = "io.michaelrocks:libphonenumber-android", version.ref = "lib-phone-number-port" }
254-
lib-phone-number-google = { module = "com.googlecode.libphonenumber:libphonenumber", version.ref = "lib-phone-number-google" }
255254

256255
# QR / Scanning
257256
zxing = { module = "com.google.zxing:core", version.ref = "zxing" }

‎services/flipcash-compose/build.gradle.kts‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,6 @@ dependencies {
5050
ksp(libs.androidx.room.compiler)
5151
implementation(libs.fingerprint.pro)
5252

53-
implementation(libs.lib.phone.number.google)
54-
5553
androidTestImplementation(libs.androidx.junit)
5654
androidTestImplementation(libs.junit)
5755
androidTestImplementation(libs.androidx.test.runner)

‎services/flipcash/build.gradle.kts‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,6 @@ dependencies {
5757

5858
implementation(libs.fingerprint.pro)
5959

60-
implementation(libs.lib.phone.number.google)
61-
6260
androidTestImplementation(libs.androidx.junit)
6361
androidTestImplementation(libs.junit)
6462
androidTestImplementation(libs.androidx.test.runner)

‎services/opencode/build.gradle.kts‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,6 @@ dependencies {
7676

7777
implementation(libs.fingerprint.pro)
7878

79-
implementation(libs.lib.phone.number.google)
80-
8179
androidTestImplementation(libs.androidx.junit)
8280
androidTestImplementation(libs.junit)
8381
androidTestImplementation(libs.androidx.test.runner)

0 commit comments

Comments
 (0)