Skip to content

Clear deprecated API usage (onBackPressed, getParcelable, launchWhen*) + up-button guard fix#55

Merged
hawkff merged 2 commits into
mainfrom
chore/deprecation-cleanup
Jun 21, 2026
Merged

Clear deprecated API usage (onBackPressed, getParcelable, launchWhen*) + up-button guard fix#55
hawkff merged 2 commits into
mainfrom
chore/deprecation-cleanup

Conversation

@hawkff

@hawkff hawkff commented Jun 21, 2026

Copy link
Copy Markdown
Owner

Summary

Tier-1 deprecation cleanup (from the modernization sweep). Clears compile-deprecation debt with
no behavior change — plus one real bug fix surfaced during review.

Changes

  • onBackPressed()OnBackPressedDispatcher (API 33 deprecation): ProfileSettings,
    ConfigEdit, RouteSettings, GroupSettings now register an OnBackPressedCallback that keeps the
    unsaved-changes guard. AssetsActivity's redundant onBackPressed { finish() } removed (default
    back already finishes a leaf activity). Matches the existing MainActivity pattern.
  • Up button now honors the unsaved-changes guard (bug fix): onSupportNavigateUp() routed
    through onBackPressedDispatcher.onBackPressed() instead of calling finish() directly.
    Previously the action-bar up/close button could silently discard edits while the system back
    button was protected — a real data-loss path (flagged by CodeRabbit).
  • getParcelable<T>IntentCompat/BundleCompat.getParcelable(..., Class) (API 33
    deprecation): ProfileSelectActivity, ConfigurationFragment.
  • launchWhenStarted/launchWhenCreatedlifecycleScope.launch for the one-shot jobs
    (ServiceButton delayed animation, AppListActivity loader); whenStartedwithStarted
    (StatsBar). All one-shot, so plain launch / withStarted are the correct non-deprecated forms.

Testing

  • CodeRabbit CLI: No findings.
  • Compile: no remaining deprecation warnings for any of these APIs.
  • Verified on-device: clean back finishes the settings screen; back and up with unsaved
    changes both show the "Changes not saved" dialog; "NO" discards and returns to the list; no
    crashes.

Notes

  • One vendored file (com.github.shadowsocks.plugin...AlertDialogFragment) still uses
    getParcelable on erased generic type params (Arg/Ret), where a BundleCompat
    migration can't supply the required Class<T> without changing the vendored API — left as-is.

Greptile Summary

This PR clears four categories of Android API deprecation debt (API 33: onBackPressed, getParcelable, launchWhenStarted/launchWhenCreated, whenStarted) with no behavior change, and fixes a real data-loss bug where the action-bar up button bypassed the unsaved-changes guard.

  • Back-navigation refactor (4 activities): onBackPressed() overrides replaced with OnBackPressedCallback registered in onCreate; onSupportNavigateUp() now routes through onBackPressedDispatcher.onBackPressed() instead of calling finish() directly, so both the system back gesture and the toolbar close/up button invoke the same unsaved-changes dialog.
  • Parcelable Compat (2 sites): Intent.getParcelableExtraIntentCompat.getParcelableExtra and Bundle.getParcelableBundleCompat.getParcelable, supplying the required Class<T> token for API 33+ correctness.
  • Coroutine lifecycle APIs (3 sites): launchWhenCreated/launchWhenStartedlifecycleScope.launch; whenStartedwithStarted; ServiceButton wraps the deferred UI mutation in withStarted { } to preserve the lifecycle gate without the deprecated API.

Confidence Score: 5/5

Safe to merge. All changes are mechanical API replacements with equivalent semantics, and the one behavioral change (up-button guard) is a genuine bug fix verified on-device.

Every changed code path was traced end-to-end: the OnBackPressedCallback correctly replaces the removed overrides, finish() in the !needSave() branch is safe because none of these activities use addToBackStack(), withStarted gating in ServiceButton preserves the original lifecycle intent, and the Compat Parcelable calls supply the required Class token. No new failure modes were introduced.

No files require special attention.

Important Files Changed

Filename Overview
app/src/main/java/io/nekohasekai/sagernet/ui/profile/ProfileSettingsActivity.kt Registers OnBackPressedCallback in onCreate; onSupportNavigateUp now routes through dispatcher — correctly fixes the up-button data-loss bug.
app/src/main/java/io/nekohasekai/sagernet/ui/GroupSettingsActivity.kt Same OnBackPressedCallback + up-button fix pattern as ProfileSettingsActivity; no fragment back-stack exists (replace without addToBackStack), so calling finish() directly in the !needSave() branch is safe.
app/src/main/java/io/nekohasekai/sagernet/ui/RouteSettingsActivity.kt Identical callback + up-button pattern applied cleanly; behavior is equivalent to the removed onBackPressed override.
app/src/main/java/io/nekohasekai/sagernet/ui/profile/ConfigEditActivity.kt OnBackPressedCallback + up-button fix applied; dirty-check logic is identical to the removed override.
app/src/main/java/io/nekohasekai/sagernet/widget/ServiceButton.kt Replaces launchWhenStarted with launch + withStarted; the delay still ticks while stopped, but the UI mutation (isIndeterminate/show) is gated on STARTED, addressing the previous review concern.
app/src/main/java/io/nekohasekai/sagernet/widget/StatsBar.kt whenStarted → withStarted; one-shot UI call, semantically equivalent, correctly drops the deprecated API.
app/src/main/java/io/nekohasekai/sagernet/ui/AppListActivity.kt launchWhenCreated → launch; loadApps() is only ever called from onCreate (lifecycle already CREATED), so the gate is no-op in practice and the change is safe.
app/src/main/java/io/nekohasekai/sagernet/ui/AssetsActivity.kt Redundant onBackPressed { finish() } removed; default back behavior is identical for a leaf activity with no registered callbacks.
app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt BundleCompat.getParcelable(..., Class) replaces the deprecated typed getParcelable; correctly supplies the required Class token.
app/src/main/java/io/nekohasekai/sagernet/ui/ProfileSelectActivity.kt IntentCompat.getParcelableExtra replaces deprecated Intent.getParcelableExtra; correct Compat API usage.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant User
    participant UpButton as Up / Back Button
    participant Dispatcher as OnBackPressedDispatcher
    participant Callback as OnBackPressedCallback
    participant Dialog as UnsavedChangesDialog
    participant Activity

    Note over UpButton,Activity: NEW behavior (both paths unified)
    User->>UpButton: press back OR tap up button
    UpButton->>Dispatcher: onBackPressed()
    Dispatcher->>Callback: invoke()
    alt "needSave() == true"
        Callback->>Dialog: show()
        alt User clicks YES
            Dialog->>Activity: saveAndExit()
        else User clicks NO
            Dialog->>Activity: finish()
        else User clicks Cancel
            Dialog-->>User: dismiss (stay)
        end
    else "needSave() == false"
        Callback->>Activity: finish()
    end

    Note over UpButton,Activity: OLD behavior (bug — up button bypassed guard)
    User->>UpButton: tap up button
    UpButton->>Activity: onSupportNavigateUp()
    Activity->>Activity: finish() guard skipped!
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant User
    participant UpButton as Up / Back Button
    participant Dispatcher as OnBackPressedDispatcher
    participant Callback as OnBackPressedCallback
    participant Dialog as UnsavedChangesDialog
    participant Activity

    Note over UpButton,Activity: NEW behavior (both paths unified)
    User->>UpButton: press back OR tap up button
    UpButton->>Dispatcher: onBackPressed()
    Dispatcher->>Callback: invoke()
    alt "needSave() == true"
        Callback->>Dialog: show()
        alt User clicks YES
            Dialog->>Activity: saveAndExit()
        else User clicks NO
            Dialog->>Activity: finish()
        else User clicks Cancel
            Dialog-->>User: dismiss (stay)
        end
    else "needSave() == false"
        Callback->>Activity: finish()
    end

    Note over UpButton,Activity: OLD behavior (bug — up button bypassed guard)
    User->>UpButton: tap up button
    UpButton->>Activity: onSupportNavigateUp()
    Activity->>Activity: finish() guard skipped!
Loading

Comments Outside Diff (1)

  1. app/src/main/java/io/nekohasekai/sagernet/widget/ServiceButton.kt, line 65-73 (link)

    P2 Lifecycle gate removed from delayed animation

    launchWhenStarted would pause the delay coroutine when the lifecycle drops below STARTED (e.g., screen off mid-connect), then resume it when the lifecycle returns to STARTED. With plain launch, the delay() ticks down regardless of lifecycle state, so isIndeterminate = true; show() can be called on the progress indicator while the activity is stopped. In most cases the brief (< 2 s) window and the delayedAnimation?.cancel() call on state changes make this harmless, but if the user backgrounds the app during the connecting state and the delay expires while stopped, the progress indicator would attempt to show on an invisible view. Consider using lifecycleScope.launch { withStarted { ... } } as a drop-in that re-attaches the lifecycle gate without the deprecated API.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Reviews (2): Last reviewed commit: "review: keep STARTED gate on delayed con..." | Re-trigger Greptile

…chWhen*)

Tier-1 deprecation cleanup, no behavior change except a bug fix noted below:

- onBackPressed() overrides -> OnBackPressedDispatcher.addCallback (ProfileSettings,
  ConfigEdit, RouteSettings, GroupSettings); AssetsActivity's redundant
  onBackPressed{finish()} removed (default back already finishes).
- onSupportNavigateUp() now routes through onBackPressedDispatcher.onBackPressed() so the
  action-bar up button honors the unsaved-changes guard too (previously it called finish()
  directly and could silently discard edits) — fixes a real data-loss path.
- intent/bundle getParcelable<T> -> IntentCompat/BundleCompat.getParcelable(..., Class)
  (ProfileSelectActivity, ConfigurationFragment) — typed, non-deprecated on API 33+.
- launchWhenStarted/launchWhenCreated -> lifecycleScope.launch (one-shot jobs: ServiceButton
  delayed animation, AppListActivity loader); whenStarted -> withStarted (StatsBar).

Verified on-device: clean back finishes; back/up with unsaved changes both show the save
dialog; no crashes.
@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Multiple Android API deprecation fixes across the app: four activities (GroupSettingsActivity, RouteSettingsActivity, ConfigEditActivity, ProfileSettingsActivity) migrate unsaved-changes back-press handling from onBackPressed() overrides to onBackPressedDispatcher callbacks; AssetsActivity drops a redundant onBackPressed() override; AppListActivity and ServiceButton replace deprecated launchWhenCreated/launchWhenStarted with launch; StatsBar switches whenStarted to withStarted; ConfigurationFragment and ProfileSelectActivity adopt BundleCompat/IntentCompat for parcelable retrieval.

Changes

Android API Modernization

Layer / File(s) Summary
Back-press dispatcher migration across settings activities
ui/GroupSettingsActivity.kt, ui/RouteSettingsActivity.kt, ui/profile/ConfigEditActivity.kt, ui/profile/ProfileSettingsActivity.kt, ui/AssetsActivity.kt
Removes onBackPressed() overrides from four activities with unsaved-changes guards and replaces each with an onBackPressedDispatcher.addCallback in onCreate. onSupportNavigateUp in each activity is updated to route through onBackPressedDispatcher.onBackPressed(). AssetsActivity simply drops its redundant onBackPressed() override that called finish().
Deprecated coroutine lifecycle launcher replacements
ui/AppListActivity.kt, widget/ServiceButton.kt, widget/StatsBar.kt
AppListActivity.loadApps() switches from lifecycleScope.launchWhenCreated to lifecycleScope.launch. ServiceButton's connecting-icon coroutine switches from launchWhenStarted to launch. StatsBar.postWhenStarted replaces whenStarted with withStarted.
BundleCompat and IntentCompat parcelable adoption
ui/ConfigurationFragment.kt, ui/ProfileSelectActivity.kt
ConfigurationFragment.onViewStateRestored replaces Bundle.getParcelable<ProxyGroup>() with BundleCompat.getParcelable(…, ProxyGroup::class.java). ProfileSelectActivity.onCreate replaces intent.getParcelableExtra<ProxyEntity>() with IntentCompat.getParcelableExtra(…, ProxyEntity::class.java).

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐇 Hoppity-hop through the API lane,
Old overrides swept out in the rain!
addCallback now guards the unsaved way,
withStarted and launch brighten the day,
BundleCompat wraps parcels just right —
The rabbit refactors, code shining bright! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: clearing deprecated APIs (onBackPressed, getParcelable, launchWhen*) and fixing up-button behavior.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description comprehensively describes the changeset across 10 files, covering four categories of API 33 deprecations and one bug fix with clear explanations and testing details.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/main/java/io/nekohasekai/sagernet/widget/ServiceButton.kt`:
- Line 24: The lifecycle-aware behavior was lost when replacing
`launchWhenStarted` with plain `lifecycleScope.launch` in ServiceButton.kt,
allowing the delayed coroutine to execute and call `show()` even when the
activity is stopped. Locate the coroutine block in ServiceButton.kt that calls
`show()` and wrap the final UI mutation with `withStarted` to restore the
lifecycle gate, ensuring the UI update only happens when the activity is in the
STARTED state, consistent with the pattern already used in StatsBar.kt.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4038ec58-1aa4-4104-aa6f-0afba40cbe5f

📥 Commits

Reviewing files that changed from the base of the PR and between c89b57f and 12a999e.

📒 Files selected for processing (10)
  • app/src/main/java/io/nekohasekai/sagernet/ui/AppListActivity.kt
  • app/src/main/java/io/nekohasekai/sagernet/ui/AssetsActivity.kt
  • app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt
  • app/src/main/java/io/nekohasekai/sagernet/ui/GroupSettingsActivity.kt
  • app/src/main/java/io/nekohasekai/sagernet/ui/ProfileSelectActivity.kt
  • app/src/main/java/io/nekohasekai/sagernet/ui/RouteSettingsActivity.kt
  • app/src/main/java/io/nekohasekai/sagernet/ui/profile/ConfigEditActivity.kt
  • app/src/main/java/io/nekohasekai/sagernet/ui/profile/ProfileSettingsActivity.kt
  • app/src/main/java/io/nekohasekai/sagernet/widget/ServiceButton.kt
  • app/src/main/java/io/nekohasekai/sagernet/widget/StatsBar.kt
💤 Files with no reviewable changes (1)
  • app/src/main/java/io/nekohasekai/sagernet/ui/AssetsActivity.kt

Comment thread app/src/main/java/io/nekohasekai/sagernet/widget/ServiceButton.kt
Plain lifecycleScope.launch (replacing launchWhenStarted) could run the delayed
progress show() while the activity is stopped. Gate the UI mutation with withStarted to
preserve the lifecycle-aware behavior (matches StatsBar).
@hawkff hawkff merged commit 04956ce into main Jun 21, 2026
5 checks passed
@hawkff hawkff deleted the chore/deprecation-cleanup branch June 21, 2026 18:27
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