Skip to content

DRY: typed AppPrefs facade — one owner for the critical/warning key + default (#162)#198

Merged
almothafar merged 3 commits into
masterfrom
162-typed-prefs-facade
Jul 19, 2026
Merged

DRY: typed AppPrefs facade — one owner for the critical/warning key + default (#162)#198
almothafar merged 3 commits into
masterfrom
162-typed-prefs-facade

Conversation

@almothafar

Copy link
Copy Markdown
Owner

Closes #162.

Problem

The critical/warning battery-level defaults (20/40) were restated as inline literals at every read site — NotificationService, BatteryLevelReceiver, MainActivity — while named constants lived in a fourth place (BatteryRangeSliderHelper). If the default ever changed, the alert engine and the UI could silently disagree.

Fix

Introduce AppPrefs, a small typed facade in util/ that is the single owner of the key + default for the critical/warning levels:

  • AppPrefs.criticalLevel(ctx) / warningLevel(ctx) — key + default, replacing the inline getInt(getString(...), 20|40) reads.
  • AppPrefs.setBatteryLevels(ctx, critical, warning) — the two slider write paths shared this pair; the write now lives beside the reads.
  • AppPrefs.DEFAULT_CRITICAL_LEVEL / DEFAULT_WARNING_LEVEL — the only place 20/40 live now.

Migrated sites: BatteryLevelReceiver, NotificationService, MainActivity (3 read/write sites). Removed BatteryRangeSliderHelper.DEFAULT_CRITICAL/DEFAULT_WARNING; the slider preference now references the facade constants, and pref_alerts.xml carries a comment tying its XML-declared defaults to them (the framework instantiates from XML, so the value can't literally share a constant there). Also dropped MainActivity's now-unused SharedPreferences/PreferenceManager imports.

Per the issue's "migrate incrementally — a full sweep can trail", this PR covers the duplicated-default offenders only. The already-single-owner clamps (clampDrainLimit, clampMinutesToMs) and other settings are left for later increments; they stay untouched and unit-tested.

Acceptance criteria

  • The 40/20 defaults exist in exactly one place, used by alert engine, receiver, and both sliders
  • No raw pref-key reads with inline defaults outside the facade for the migrated settings
  • Clamping behavior unchanged and unit-tested (clamps untouched; existing tests remain green)
  • BatteryRangeSliderHelper.DEFAULT_* moved to the facade (slider preference delegates to it)

Tests

  • New AppPrefsTest (Robolectric): defaults fall back to the single owner, stored values read back, setBatteryLevels round-trips under the shared keys.
  • Full :app:testDebugUnitTest suite green.

Not covered here (deferrable per #162)

  • Bundling critical+warning into a LevelThresholds value type (Data Clumps) — better as a follow-up.
  • Folding drainLimitPph/clamps and the remaining settings into the facade — later increments.

🤖 Generated with Claude Code

almothafar and others added 3 commits July 19, 2026 12:24
)

Introduce AppPrefs as the single owner of the critical/warning battery-level
key + default. The 20/40 literals previously restated in NotificationService,
BatteryLevelReceiver, MainActivity and BatteryRangeSliderHelper now live only
in AppPrefs.DEFAULT_CRITICAL_LEVEL / DEFAULT_WARNING_LEVEL, so the alert engine,
receiver and both sliders can no longer disagree.

- Migrate the read sites to AppPrefs.criticalLevel/warningLevel(context).
- Route the two slider write paths' shared pair through AppPrefs.setBatteryLevels.
- Remove BatteryRangeSliderHelper.DEFAULT_CRITICAL/DEFAULT_WARNING; the slider
  preference now references the facade constants, and pref_alerts.xml carries a
  comment tying its XML-declared defaults to them.
- Drop MainActivity's now-unused SharedPreferences/PreferenceManager imports.
- Add Robolectric coverage for the facade's defaults, read-back and write.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…lt (#162)

Code review flagged the class Javadoc's "the literals now live only ... here" as
overstating it: pref_alerts.xml still declares the slider's XML default (20/40),
which the framework cannot share as a constant. Reword to say every Java read
derives its default from the constants, and name the XML restatement as the one
remaining (already comment-tied) exception, so the doc matches reality.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ade, XML drift guard (#162)

Three items the two-axis review raised as deferrable, now done:

1. Data Clumps — introduce model.LevelThresholds (record) so the critical/warning
   pair travels as one value instead of a loose (int, int) that could be swapped.
   AppPrefs.batteryLevels()/setBatteryLevels() and BatteryRangeSliderHelper.clampPair()
   now take/return it; the sliders and MainActivity unwrap only at the widget edge
   (HorseshoeProgressBar stays int-based to remain a reusable, app-agnostic widget).

2. Fold the drain limit into the facade — move DEFAULT/MIN/MAX_DRAIN_LIMIT_PPH and
   the clamp from BatteryRateTracker into AppPrefs as drainLimitPph()/clampDrainLimit(),
   the "single owner of key + default + clamp" the issue asks for. This also avoids a
   util->service dependency. Repoint FastDrainDetector and BatteryDetailsFragment;
   the pure clamp stays unit-tested (test moved alongside it).

3. Drift guard — AppPrefsTest now parses pref_alerts.xml and asserts the slider's
   XML-declared criticalDefault/warningDefault equal the AppPrefs constants, so the
   one framework-forced restatement can no longer silently diverge.

Full suite: 445 tests, 0 failures.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@almothafar
almothafar merged commit 83007c8 into master Jul 19, 2026
2 checks passed
@almothafar
almothafar deleted the 162-typed-prefs-facade branch July 19, 2026 10:13
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.

DRY: typed preferences facade — one owner for keys, defaults and clamping

1 participant