Repository navigation
feat(alerts): notify about wishlist sales and free games - #4
Merged
Merged
Conversation
The app now tells you when a game you saved goes on sale, and when a game
becomes free to keep - with the name, the new price and the discount - and it
does so with the app closed.
The background check runs on two mechanisms, because one is never enough.
WorkManager's periodic worker is the officially supported way to do background
work and it survives reboots, but a periodic worker is a request rather than a
promise: the system defers it, Doze pushes it out, and battery managers delay
it further. An alarm is punctual and setAndAllowWhileIdle is delivered inside a
Doze window, but alarms do not survive a reboot on their own. Both end up
running the same idempotent check, so overlapping triggers cannot double-notify.
Only changes are announced. A wishlisted game that starts being discounted is
news; one that is still discounted next hour is not. A game missing from the
feed leaves its record alone, because the feed holds only the top few dozen
discounts per store and dropping out of it says nothing either way. The record
is only cleared when the game is seen and is not discounted, which is the one
observation that proves the sale ended.
Five bugs are fixed along the way, all of which were found by running this on
a device rather than by reading the code:
* The check never ran at all. WishlistRepository.loadOnce() awaited a
DataStore flow with collect, which never returns, so the worker hung with
no error, no crash report and no notification.
* Two scheduling policies, both wrong. REPLACE cancelled the in-flight check
and, because the two triggers can fire together, that became a cancellation
storm in which no check ever completed. KEEP fixed that but let a work
stuck in a queued state block every later request under the same name.
* The release build crashed on launch and the debug build did not. WorkManager
keeps its schedule in a Room database, and Room instantiates the generated
implementation reflectively - a call R8 cannot see, so R8 removed it. Every
debug run passed; the crash existed only in the build that would have been
shipped.
* A notification's URI arrived with data == null, so tapping one opened the
app and went nowhere. The identity now travels in intent extras.
* A free game was announced twice, as "is free" and as "100% off".
Also in this release:
* The release workflow signs with the keystore from a repository secret and
verifies the signature before publishing, and refuses to publish at all if
the secret is missing. A release signed with the wrong key installs once
and then refuses to update.
* versionCode 2, versionName 1.1.0-release.
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.
Adds deal alerts: a notification when a game you saved goes on sale, and when a game becomes
free to keep — with the name, the new price and the discount — delivered with the app closed.
How the background check works
The check runs on two mechanisms, because one is never enough:
AlarmManageralarm (setAndAllowWhileIdle)Both end up running the same idempotent check, so overlapping triggers cannot double-notify. The
exact-alarm permission is asked for rather than assumed: if it is refused, the scheduler falls back
to an inexact alarm, which still works inside Doze — a later notification beats none.
What gets announced
Only changes, because the check runs on a timer and sees the same deals over and over:
A game that is still discounted next hour is not news. A game missing from the feed leaves its
record alone — the feed holds only the top few dozen discounts per store, so dropping out of it says
nothing either way. The record is cleared only when the game is seen and is not discounted, which
is the one observation that proves the sale ended.
The first check announces nothing. Without a previous observation, nothing can be shown to have
become anything, and announcing the current state would mean one notification per free game the
moment the feature is switched on.
Bugs found and fixed along the way
All five were found by running this on a device, not by reading the code:
WishlistRepository.loadOnce()awaited a DataStore flow withcollect, which never returns — the flow stays open for the life of the process. The workerstarted, blocked on the wishlist load, and hung with no error, no crash report and no
notification.
REPLACEcancelled the in-flight check, and becausethe two triggers can fire together that became a cancellation storm in which no check ever
completed.
KEEPfixed that but let a work stuck in a queued state block every later requestunder the same name, so the checks stopped silently.
schedule in a Room database, and Room instantiates the generated implementation reflectively — a
call R8 cannot see, so R8 removed it. Every debug run passed; the crash existed only in the build
that would have been shipped.
data == null. The intent that was built was correct andthe intent that arrived had no data, so tapping a notification opened the app and went nowhere.
The identity now travels in intent extras, which are copied verbatim.
Release signing
The release workflow now signs with the keystore from a repository secret, verifies the signature
before publishing, and refuses to publish at all if the secret is missing. A release signed with
the wrong key installs once and then refuses to update, which is a worse outcome than no release.
Verification
CN=Ivan Bizimoski, APK Signature Scheme v2),reports
versionName=1.1.0-release/versionCode=2, and was installed and launched on adevice with no crash.
system started the app itself when the alarm fired, and the notifications were posted. Tapping one
opens that game's page, showing the same price the notification did.