<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Satechs LLC</title>
    <description>The latest articles on DEV Community by Satechs LLC (@satechss).</description>
    <link>https://dev.to/satechss</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4200317%2Fd4a208ed-f200-4647-adec-8d8839598a06.png</url>
      <title>DEV Community: Satechs LLC</title>
      <link>https://dev.to/satechss</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/satechss"/>
    <language>en</language>
    <item>
      <title>Making a reminder actually ring: lessons from Android and iPhone</title>
      <dc:creator>Satechs LLC</dc:creator>
      <pubDate>Thu, 22 Oct 2026 11:13:48 +0000</pubDate>
      <link>https://dev.to/satechss/making-a-reminder-actually-ring-lessons-from-android-and-iphone-32da</link>
      <guid>https://dev.to/satechss/making-a-reminder-actually-ring-lessons-from-android-and-iphone-32da</guid>
      <description>&lt;p&gt;I built a reminder app called &lt;a href="https://alarvo.app" rel="noopener noreferrer"&gt;Alarvo&lt;/a&gt; where the important reminders behave like an alarm clock: full screen, through Do Not Disturb and Silent mode, ringing until you tap Done. I thought the hard part would be the alarm screen. It wasn't. The hard part was making sure the alarm fires at all.&lt;/p&gt;

&lt;p&gt;Alarvo runs on Android and iPhone, and the two platforms could hardly be more different here. Here's what I learned, in case you're building anything time-critical on either.&lt;/p&gt;

&lt;h2&gt;
  
  
  Android: the alarm is easy, firing it is not
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Pick the right AlarmManager API
&lt;/h3&gt;

&lt;p&gt;Most reminder apps schedule with &lt;code&gt;setExactAndAllowWhileIdle&lt;/code&gt;. It looks right: exact, and allowed while idle. In practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Doze can still defer it.&lt;/li&gt;
&lt;li&gt;While the device is idle, it's rate-limited to roughly once every 9 minutes per app.&lt;/li&gt;
&lt;li&gt;The user can't see it, so when it fails, nobody knows why.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;setAlarmClock&lt;/code&gt; is different. The platform treats it as a real, user-facing alarm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;info&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AlarmManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AlarmClockInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;triggerAtMillis&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;showIntent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;alarmManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAlarmClock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;firePendingIntent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's exempt from Doze and App Standby buckets, and OEM battery managers are least likely to touch it. As a bonus, it puts the alarm icon in the status bar. That icon is a free, always-on signal to the user that the app is armed.&lt;/p&gt;

&lt;p&gt;The catch: &lt;code&gt;setAlarmClock&lt;/code&gt; is for things the user genuinely asked to be woken for. I use it only for &lt;strong&gt;Critical&lt;/strong&gt; reminders and for tasks the user explicitly set to ring. Quieter reminders go through &lt;code&gt;setExactAndAllowWhileIdle&lt;/code&gt; or plain notifications. Using the strong API for everything is abusing the privilege, and invites the platform (and Play review) to take it away.&lt;/p&gt;

&lt;p&gt;On Android 12+, check before you schedule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;canSchedule&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;VERSION&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SDK_INT&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nc"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;VERSION_CODES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;S&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;alarmManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;canScheduleExactAlarms&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;setAlarmClock&lt;/code&gt; throws a &lt;code&gt;SecurityException&lt;/code&gt; without the permission. Catch it and tell the user, instead of crashing or silently dropping their reminder.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Alarms disappear more often than you think
&lt;/h3&gt;

&lt;p&gt;Scheduled alarms don't survive several everyday events. Most apps handle &lt;code&gt;BOOT_COMPLETED&lt;/code&gt; and stop there. My receiver also listens for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;MY_PACKAGE_REPLACED&lt;/code&gt;&lt;/strong&gt;: an app update clears your alarms. Without this, every release silently disarms every reminder for every user until they next open the app.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;LOCKED_BOOT_COMPLETED&lt;/code&gt;&lt;/strong&gt;: fires before the user unlocks a direct-boot device, much earlier than &lt;code&gt;BOOT_COMPLETED&lt;/code&gt;. Handling both is harmless if rescheduling is idempotent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;TIMEZONE_CHANGED&lt;/code&gt; and &lt;code&gt;TIME_SET&lt;/code&gt;&lt;/strong&gt;: if you store wall-clock times ("8:00 PM"), flying somewhere changes when that actually is. Recompute.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Ringing through Do Not Disturb
&lt;/h3&gt;

&lt;p&gt;For the alarm itself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Play the sound with &lt;code&gt;AudioAttributes.USAGE_ALARM&lt;/code&gt;, so it uses the alarm stream and cuts through silent mode. Use &lt;code&gt;USAGE_NOTIFICATION&lt;/code&gt; for quieter levels; the alarm stream stays audible when the user has deliberately silenced notifications, and only the reminders that earned it should use it.&lt;/li&gt;
&lt;li&gt;Show it with a full-screen intent over the lock screen. On Android 14+, &lt;code&gt;USE_FULL_SCREEN_INTENT&lt;/code&gt; is only granted at install to apps whose core function is alarms or calls, and the user can revoke it. Check &lt;code&gt;NotificationManager.canUseFullScreenIntent()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Run the ringing in a foreground service, so it isn't killed mid-alarm.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. The part no API fixes: phone makers
&lt;/h3&gt;

&lt;p&gt;Even with all of the above, OEM battery managers can kill your app: Samsung's "sleeping apps", Xiaomi's Autostart, and others. None of them tell the user they've done it.&lt;/p&gt;

&lt;p&gt;You can't fix this in code, so I made it visible instead. Alarvo has a reliability screen that checks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;exact-alarm permission&lt;/li&gt;
&lt;li&gt;notifications enabled&lt;/li&gt;
&lt;li&gt;full-screen intent allowed&lt;/li&gt;
&lt;li&gt;Do Not Disturb access (&lt;code&gt;isNotificationPolicyAccessGranted&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;battery optimisation (&lt;code&gt;PowerManager.isIgnoringBatteryOptimizations&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;the maker-specific settings, with step-by-step instructions for each brand&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Prove it: ship a test alarm
&lt;/h3&gt;

&lt;p&gt;The last piece is a &lt;strong&gt;test alarm&lt;/strong&gt;: the user locks the phone, puts it down, and 15 seconds later a real alarm rings, through exactly the same path real reminders use (same scheduler, same receiver, same service). If anything in the chain is broken on that particular phone, they find out now and not at 3 AM.&lt;/p&gt;

&lt;p&gt;I think every app that promises to wake you should ship one.&lt;/p&gt;

&lt;h2&gt;
  
  
  iPhone: AlarmKit changes the rules
&lt;/h2&gt;

&lt;p&gt;On iPhone, the problem used to be the opposite. Background execution is tightly controlled, and a third-party app could only remind you with a notification: a short sound that Silent mode and Focus would happily mute. There was no way to build a real alarm.&lt;/p&gt;

&lt;p&gt;AlarmKit (iOS 26) fixes that. Your app asks once for permission:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="kt"&gt;AlarmManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;requestAuthorization&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and then schedules alarms that the system itself owns and rings: full screen on the lock screen, through Silent mode and Focus, whether or not your app is running.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="kt"&gt;AlarmManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The buttons on the alarm are App Intents. I pass a &lt;code&gt;stopIntent&lt;/code&gt; for &lt;strong&gt;Done&lt;/strong&gt; and a &lt;code&gt;secondaryIntent&lt;/code&gt; for &lt;strong&gt;Snooze&lt;/strong&gt;, so the app learns which one you pressed and can mark the reminder done, or bring it back, even if it was never opened.&lt;/p&gt;

&lt;p&gt;What you don't get is the Android-style escalation ladder: the system rings the alarm, and you don't control it once it's going. That's a fair trade. Because the OS owns the alarm, there's no battery manager to fight and no reliability screen needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The app
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://alarvo.app" rel="noopener noreferrer"&gt;Alarvo&lt;/a&gt; has three reminder levels (quiet notification, sound, or full alarm), an AI planner that turns "gym at 6, meds at 9" into reminders you check before saving, repeats like "3 days after I last did it," and one account that syncs across Android and iPhone.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Website: &lt;a href="https://alarvo.app" rel="noopener noreferrer"&gt;https://alarvo.app&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Google Play: &lt;a href="https://play.google.com/store/apps/details?id=app.alarvo" rel="noopener noreferrer"&gt;https://play.google.com/store/apps/details?id=app.alarvo&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you've fought background limits on either platform, I'd love to hear what worked for you, especially on Android brands I haven't tested.&lt;/p&gt;

</description>
      <category>android</category>
      <category>mobile</category>
      <category>showdev</category>
      <category>ios</category>
    </item>
  </channel>
</rss>
