Skip to content

fix(android): guard the remaining NewApi lint findings - #252

Merged
V3RON merged 1 commit into
fix/android-notifications-api-26from
fix/android-remaining-newapi
Sep 2, 2026
Merged

fix(android): guard the remaining NewApi lint findings#252
V3RON merged 1 commit into
fix/android-notifications-api-26from
fix/android-remaining-newapi

Conversation

@V3RON

@V3RON V3RON commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

What is this?

Two further crashes on older Android versions, from the same cause as #242: platform APIs called without a version guard. Registering the internal event receiver terminates the process on Android 7.0–7.1, and any widget using adaptive grid columns terminates it on anything below Android 12. A third path opens a settings screen that does not exist before Android 8.0.

Stacked on #251.

How does it work?

Context.registerReceiver(BroadcastReceiver, IntentFilter, int) — the flags overload — is API 26, so on 24–25 it raised NoSuchMethodError. It now goes through ContextCompat.registerReceiver, which dispatches to the flags overload on API 33+ and, below that, registers through the long-standing four-argument overload using a signature-level permission that AndroidX declares and merges into the consuming application. Non-exported semantics are preserved rather than silently dropped.

GridCells.Adaptive is annotated @RequiresApi(31) by Glance and was reachable whenever a payload set columns to an "a:<n>" value. It is now used only on API 31+, falling back below that to the same GridCells.Fixed(2) the function already used for unparseable column values, since column resolution has no access to the widget's measured width.

Settings.ACTION_APP_NOTIFICATION_SETTINGS resolves to no activity before API 26, so startActivity threw ActivityNotFoundException. It now falls back to the application details screen.

Two private helpers that build API 36 progress-style objects gain @RequiresApi(36). Their behaviour was already correct — both are only reached from inside SDK_INT >= 36 branches — but lint reasons across method boundaries only through annotations, so it could not see the guard. The annotation changes nothing at runtime and additionally makes lint verify that every caller stays guarded.

androidx.core and androidx.annotation were previously only on the compile classpath transitively through Glance, and are now declared explicitly since this module uses them directly.

Why is this useful?

These are the same failure mode as the widget crash, reached through different features, and they affect the Android versions Expo applications default to supporting. Resolving them also clears the last findings that prevent enforcing this check automatically.

Enabling Android Lint's NewApi check (to be flipped on in a later PR in
this stack) surfaced 15 findings; the two earlier PRs in the stack fixed
4. This fixes the remaining 11 across four call sites:

- VoltraEventBus.addListener used the flags overload of
  Context.registerReceiver(BroadcastReceiver, IntentFilter, int), which is
  API 26. On API 24-25 it doesn't exist and ART throws NoSuchMethodError
  the first time a listener is added, killing the process (same defect
  class as #242). Switched to
  androidx.core.content.ContextCompat.registerReceiver, which dispatches
  to the right registerReceiver() call for the running OS version. Below
  API 26 it obtains a synthetic, signature-protected
  "<package>.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION" (auto-merged into
  the app's manifest by androidx.core's own manifest) and registers via
  the classic 4-arg registerReceiver(receiver, filter, permission,
  handler) overload that has existed since API 1, which preserves
  not-exported semantics because only code signed with the same
  certificate can hold that permission.
- extractGridCells in VoltraLazyVerticalGrid built a
  GridCells.Adaptive unconditionally for "a:<n>" column specs.
  GridCells.Adaptive is @RequiresApi(31), so widgets using it crashed on
  API 24-30. Now guarded on SDK_INT >= S, falling back to the function's
  existing Fixed(2) fallback below API 31. extractGridCells has no access
  to the widget's measured size, so a width-derived column count isn't
  available without plumbing LocalSize through, which is out of scope
  here.
- openPromotedNotificationSettings built an Intent for
  Settings.ACTION_APP_NOTIFICATION_SETTINGS unconditionally. The
  action/extra constants are inlined Strings (no field-missing risk), but
  no activity resolves that action on API 24-25, so startActivity threw
  ActivityNotFoundException for a call reachable from JS on any device.
  Now guarded on SDK_INT >= O, falling back to
  ACTION_APPLICATION_DETAILS_SETTINGS (API 9+) below that.
- toNativeSegment/toNativePoint in VoltraNotificationManager are only
  ever called from inside an `if (SDK_INT >= 36)` block, so they were
  already safe at runtime, but NewApi only reasons across method
  boundaries through @RequiresApi. Annotated both with @RequiresApi(36)
  (matching the file's literal-36 convention) so lint can verify the
  guard; no behavior change.

androidx.core and androidx.annotation were previously only present on
the compile classpath transitively via Glance/WorkManager. Declared both
explicitly in build.gradle: androidx.core:core:1.9.0 (the minimum for
the ContextCompat.registerReceiver flags overload, and already the
version resolved transitively via work-runtime-ktx, so this doesn't
change what's bundled) and androidx.annotation:annotation:1.8.1
(matching the version Glance already pulls in).
@V3RON
V3RON force-pushed the fix/android-remaining-newapi branch from f96db1b to e4cb0b0 Compare September 2, 2026 07:41
@V3RON
V3RON merged commit d40b069 into main Sep 2, 2026
18 of 27 checks passed
@V3RON
V3RON deleted the fix/android-remaining-newapi branch September 2, 2026 08:53
V3RON added a commit that referenced this pull request Sep 2, 2026
## What is this?

Android Lint now runs in CI and fails the build when it finds a call to
an API newer than the module's minimum SDK version. This is the check
that would have caught #242, #251 and #252 before any of them shipped.

Stacked on #252.

## How does it work?

The module has carried `lintOptions { abortOnError false }` since it was
created, so `NewApi` findings were reported and then ignored. Nothing
else in the toolchain covers this: the module compiles with `compileSdk
36` against an `android.jar` containing every API while declaring a
floor of 24, and neither the Kotlin compiler nor core library desugaring
checks platform API calls against that floor. Lint is the only check,
and it was disabled.

`abortOnError` is now opt-in through `-PvoltraLintStrict`, and the CI
job that already prebuilds the example application passes that flag.
Gating it this way keeps enforcement in CI without changing the outcome
of lint runs in consuming applications, which receive this module as a
source subproject rather than a published artifact.

Behaviour was confirmed in both directions: with a deliberate API 31
call added to the module, `lintDebug` passes without the flag and fails
with it, naming the offending file and required API level. On this
branch the module reports no `NewApi` findings and no error-severity
findings.

## Why is this useful?

Every fix below this in the stack addresses a crash that reached
released versions because nothing checked platform API calls against the
supported floor. Without an automatic check the same class of bug
returns the next time a widget or notification feature adopts a newer
API, and it surfaces as a process termination on a user's device rather
than a failed build.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant