fix(android): guard the remaining NewApi lint findings - #252
Merged
V3RON merged 1 commit intoSep 2, 2026
Conversation
V3RON
force-pushed
the
fix/android-remaining-newapi
branch
from
September 2, 2026 05:36
3992a8f to
f96db1b
Compare
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
force-pushed
the
fix/android-remaining-newapi
branch
from
September 2, 2026 07:41
f96db1b to
e4cb0b0
Compare
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 raisedNoSuchMethodError. It now goes throughContextCompat.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.Adaptiveis annotated@RequiresApi(31)by Glance and was reachable whenever a payload setcolumnsto an"a:<n>"value. It is now used only on API 31+, falling back below that to the sameGridCells.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_SETTINGSresolves to no activity before API 26, sostartActivitythrewActivityNotFoundException. 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 insideSDK_INT >= 36branches — 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.coreandandroidx.annotationwere 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.