Skip to content

perf(phone): move PhoneUtils libphonenumber init off the main thread#1072

Merged
bmc08gt merged 4 commits into
code/cashfrom
perf/phoneutils-off-main-thread
Jul 12, 2026
Merged

perf(phone): move PhoneUtils libphonenumber init off the main thread#1072
bmc08gt merged 4 commits into
code/cashfrom
perf/phoneutils-off-main-thread

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Jul 12, 2026

Copy link
Copy Markdown
Collaborator

Moves PhoneUtils' heavy libphonenumber initialization off the main thread to reduce onboarding/startup jank and ANR risk on low-end devices.

PhoneUtils (a @Singleton) previously did its expensive work — PhoneNumberUtil.createInstance() (metadata blob load) plus enumerating ~250 supportedRegions with per-region locale/flag lookups — in its constructor. Because it's @Inject lateinit in MainActivity, that ran synchronously on the main thread at startup. This surfaced during triage of an onboarding ANR on a low-end device (TECNO BG6m, 32-bit, 1.87 GB RAM).

bmc08gt added 4 commits July 11, 2026 10:37
…load

Stop PhoneVerificationViewModel from reading phoneUtils.defaultCountryLocale
eagerly in initialState (forcing main-thread work before libphonenumber is
loaded). initialState now uses CountryLocale.Stub; the init coroutine calls
ensureLoaded() on the Default dispatcher inside a try/catch (rethrows
CancellationException; logs other failures via trace so input observation
still starts even if metadata load fails), then dispatches OnCountrySelected
with the real locale. Phone-number input observation is extracted to
observePhoneNumberInput() and started only after ensureLoaded() completes,
so no parse/format ever runs before the metadata is ready.
…bservability

PhoneCountryCodeScreenContent was reading phoneUtils.countryLocales directly
from a plain @volatile field. Compose does not snapshot-track plain field reads,
so the picker rendered empty and never recomposed to recover after the async load.

Route the list through ViewModel State (countryLocales: List<CountryLocale>) so
it is observed via the existing collectAsStateWithLifecycle() call. The new
OnCountryLocalesLoaded event is dispatched in the init coroutine after
ensureLoaded() completes, alongside the existing OnCountrySelected dispatch.

Also guard the default-locale selection so OnCountrySelected is only dispatched
when the loaded default is non-Stub — prevents a +0 / empty-country-code field
if ensureLoaded() threw and left defaultCountryLocale == Stub.

ContactCoordinator.performSync() gains an ensureLoaded() call before formatting
E.164 numbers, guarding against the first sync racing ahead of the app-startup
warm-up and using an empty region hint for toE164.
@bmc08gt bmc08gt self-assigned this Jul 12, 2026
@bmc08gt bmc08gt merged commit d7ed2a8 into code/cash Jul 12, 2026
3 checks passed
@bmc08gt bmc08gt deleted the perf/phoneutils-off-main-thread branch July 12, 2026 02:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant