Push notifications are the most efficient growth mechanism mobile software has ever had. They are also the single biggest reason most people have a complicated relationship with their phone. The same primitive that gets your delivery driver a tip is the one that interrupts dinner to remind you about a sale.
A hydration app is almost the worst possible offender here, if you let it be. Reminders every two hours, all day, every day, forever. By the end of a month most users have either silenced the app entirely or learned to ignore its little waves of guilt. Both outcomes defeat the purpose.
We thought about this a lot when building Drimin. Here's the discipline we settled on.
The first rule: do less, not more
Most reminder apps default to "every two hours from when you wake until when you sleep," which is something like eight reminders a day. We default to fewer — a light schedule that you can tune up if you want to, but that starts on the assumption you'd rather get one too few than one too many.
The number that matters is "the smallest schedule that still nudges you to drink." For most people that's three or four reminders, evenly spaced through the active part of the day. Anything more becomes background noise. Once a notification is background noise, it isn't doing its job.
Reminders that stop when you're done
This is the single most useful thing we added. The moment your day's intake hits the goal, Drimin cancels every remaining reminder for that day. You didn't ask to be congratulated; you asked to be reminded. If the reminding is done, the app gets out of the way.
On the engineering side, this is just flutter_local_notifications's
cancel-by-id mechanism, called from the same code path that detects the goal
crossing. Conceptually, though, it changes the whole feeling of the app from
"nagging" to "helpful." The same notification that would have felt patronising at
4 p.m. simply doesn't fire if you're already at 2.5 L by lunch.
One channel per sound, properly
Android's notification channel system is one of those APIs that quietly punishes you if you treat it as bureaucracy. Channels are immutable for the user-controllable attributes — importance, sound, vibration pattern. If you create a channel called "Reminders" with the bell sound, then later change your sound to the chime, you cannot update that channel. You have to create a new one and route subsequent notifications to it.
Most apps don't. They post their first channel, never touch it again, and silently
ignore the user's sound preference. Drimin creates a separate channel per
ReminderSound in our enum, and posts each notification to the channel
matching the currently configured sound. Change your sound in Settings and the very
next reminder really does play it.
It's a small thing. It's also the kind of small thing that defines whether an app's Settings screen is honest or decorative.
The best notification UX is the one that earns the right to wake you up, and stops as soon as it doesn't need to.
DND windows, respected by default
Drimin has a "do not disturb" range in Settings — usually your sleep hours. Any reminder scheduled inside that range is silently dropped, not just muted. This matters because some Android OEMs will still wake the screen for a "silenced" notification, which is the opposite of what you wanted.
We also respect the system DND state. If you've put your phone in Do Not Disturb
for a meeting, Drimin's reminders are queued through the standard
NotificationChannel importance flags, which means they are
suppressed without the app needing to know about it. We do not request the
POLICY_ACCESS_BYPASS_DND permission, because there is no world in
which a water reminder should override a system-level quiet.
Exact alarms, used sparingly
Android 12 introduced two related permissions: SCHEDULE_EXACT_ALARM and
the more recent USE_EXACT_ALARM. They allow an app to fire a notification
at the minute you asked for, rather than batched into the next window of system
wake-ups. Most apps don't need them. Reminder apps do, because "remind me at
9:00" should not become "remind me sometime in the next half hour."
We use exact alarms only for user-configured reminder times. Background housekeeping (database VACUUMs, widget refreshes) uses the standard inexact scheduler. The user's explicit "remind me at this time" deserves precision; the app's internal chores do not.
The notification itself, in three principles
When a reminder does fire, three small choices make it less annoying:
- One line, no caps, no emoji. "Time for a glass" beats "💧 Hey there! Don't forget to hydrate! 💦" every day of the week, for everyone who isn't fifteen.
- Inline action. The notification has a "Log a cup" action that records a default-sized drink without opening the app. Most reminders should be actionable in place; opening the app is a tax.
- Auto-cancel. Once you log the cup (in-app, via widget, or via the action), the notification clears itself. Lingering reminders for things you've already done are the worst.
What we choose not to do
A list of common patterns we deliberately don't ship:
- No "you haven't opened Drimin in a week" reactivation pings. If we've lost you, we've lost you fairly.
- No streak notifications. Streaks are a manipulation pattern dressed up as motivation. We show your streak in the app; we don't push it.
- No "X people in your city drank Y glasses today" social notifications. We have no servers and no friends-of-friends graph for a reason. See Privacy-first by default.
- No upsells, no surveys, no "rate us on the Play Store" prompts.
Where the widget takes over
For most users, the right answer to "should this be a notification?" is "no, it should be the home-screen widget." If the goal is glanceable information rather than an interrupt, the widget is the better surface. See Home-screen widgets as a calm interface for the other half of this argument. Together, the two surfaces let Drimin send drastically fewer notifications than the average reminder app while still being useful many times a day.
For the philosophical companion, see Designing for stillness and The quiet case for water.