Building a Reminder or Scheduled Task Application
Course: Android App Development
Topic: Building a Reminder or Scheduled Task Application
Course Link: Android App Development Course
Demo Link: Register for a Course Demo
Website: JustAcademy
1. Introduction to Reminder and Scheduled Task Applications
A reminder or scheduled task application helps users organize activities, remember important events, and receive notifications at specific times. Examples include medication reminders, assignment deadlines, meeting alerts, bill payment notifications, and daily task planners.
In Android development, these applications combine user interface design, date and time selection, data storage, background task scheduling, and notification management. A reliable implementation must handle application restarts, device reboots, background execution restrictions, and notification permissions.
Learning Objectives
- Understand the architecture of a reminder application.
- Create an interface for adding, editing, and viewing reminders.
- Accept and validate date and time inputs.
- Store reminder information locally using Room or another suitable persistence solution.
- Schedule background work using Android scheduling APIs.
- Display notifications at appropriate times.
- Handle permissions, time changes, and scheduling failures.
- Test and improve the reliability of a reminder application.
2. Understanding the Main Components
| Component |
Purpose |
Example |
| User Interface |
Allows users to create and manage reminders. |
Reminder form and task list. |
| Data Model |
Defines the information stored for each reminder. |
Title, description, and scheduled time. |
| Local Database |
Stores reminders so they remain available after the app closes. |
Room database. |
| Scheduling Mechanism |
Arranges work or alarms for a future time. |
AlarmManager or WorkManager. |
| Notification System |
Displays an alert to the user. |
Reminder notification. |
| Receiver or Worker |
Processes a scheduled event or background task. |
BroadcastReceiver or Worker. |
3. Choosing the Correct Android Scheduling API
Choosing the scheduling mechanism is an important design decision. Not every reminder requires an exact alarm, and not every background task should be implemented with AlarmManager.
| API |
Best Use Case |
Important Behavior |
| AlarmManager |
User-facing reminders that need to trigger at a particular time. |
Exact alarms may require special access or permissions, depending on Android version and app use case. |
| WorkManager |
Deferrable background tasks that should run reliably. |
Execution depends on system scheduling and constraints; it is not a precise clock alarm. |
| Handler or coroutine delay |
Short-lived in-process delays while an app is active. |
Not reliable for reminders that must survive app termination or device restart. |
| JobScheduler |
System-managed background jobs with constraints. |
Suitable for deferred jobs, not precise user-facing alarms. |
Key recommendation: Use AlarmManager when a user-facing reminder must trigger at a specific time. Use WorkManager for deferrable work, such as synchronizing task data or refreshing content. A recurring reminder can require rescheduling logic after each occurrence.
4. Planning the Application Structure
A maintainable reminder application separates the interface, business logic, data storage, and scheduling responsibilities.
- Presentation layer: Activities, Fragments, or Jetpack Compose screens.
- ViewModel: Manages screen state and responds to user actions.
- Repository: Coordinates database operations and reminder scheduling.
- Database layer: Stores reminders using Room.
- Scheduling layer: Creates, updates, and cancels alarms or background work.
- Notification layer: Creates notification channels and displays reminder alerts.
Example Reminder Data Model
A reminder should contain enough information to display the task and determine when it should run.
data class Reminder(
val id: Int,
val title: String,
val description: String,
val triggerAtMillis: Long,
val isEnabled: Boolean,
val isRecurring: Boolean
)
This Kotlin data class illustrates the concept. In a real application using Room, annotate the entity with @Entity, define a primary key, and configure the DAO and database. The stored time is represented as milliseconds since the Unix epoch.
5. Designing the Reminder Creation Screen
The creation screen should make it easy to enter a task and select when it should occur.
Recommended UI Fields
- Reminder title: A short description, such as “Submit project report”.
- Notes: Optional additional details.
- Date picker: Selects the reminder date.
- Time picker: Selects the reminder time.
- Repeat option: Allows daily, weekly, or other supported recurrence patterns.
- Save button: Validates the input and saves the reminder.
Example Compose Interface
@Composable
fun ReminderForm(
title: String,
onTitleChange: (String) -> Unit,
onSave: () -> Unit
) {
Column(
modifier = Modifier.padding(16.dp)
) {
OutlinedTextField(
value = title,
onValueChange = onTitleChange,
label = { Text("Reminder title") },
modifier = Modifier.fillMaxWidth()
)
Button(
onClick = onSave,
modifier = Modifier.padding(top = 12.dp)
) {
Text("Save Reminder")
}
}
}
This example shows only the title field and save action. A complete screen should also include date and time selection, validation messages, recurrence settings, and loading or error states.
6. Selecting and Validating Date and Time
The application must convert the selected date and time into a valid timestamp before scheduling the reminder.
Validation Rules
- Reject an empty or whitespace-only title.
- Ensure the selected date and time are valid.
- Check whether the selected time is in the past.
- Explain to the user when a past time is not allowed.
- Handle time zone changes according to the application's reminder policy.
- Prevent accidental duplicate reminders when the user taps Save repeatedly.
Example Timestamp Conversion
import java.time.LocalDateTime
import java.time.ZoneId
fun toTriggerTime(
dateTime: LocalDateTime
): Long {
return dateTime
.atZone(ZoneId.systemDefault())
.toInstant()
.toEpochMilli()
}
This example uses the device's current time zone. For reminders tied to a local wall-clock time, such as “every day at 8 AM,” the application should explicitly define how changes to the device time zone are handled.
7. Saving Reminders in a Local Database
Saving reminders in a database is essential because an application's in-memory data disappears when the process ends. Room provides a convenient abstraction over SQLite and supports structured queries and observable data.
Example Room Entity
@Entity(tableName = "reminders")
data class ReminderEntity(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
val title: String,
val description: String = "",
val triggerAtMillis: Long,
val isEnabled: Boolean = true
)
DAO Example
@Dao
interface ReminderDao {
@Insert
suspend fun insert(
reminder: ReminderEntity
): Long
@Query("SELECT * FROM reminders ORDER BY triggerAtMillis")
fun observeAll(): Flow<List<ReminderEntity>>
@Delete
suspend fun delete(
reminder: ReminderEntity
)
}
When the user saves a reminder, the repository should persist the record and schedule its corresponding alarm. When the user edits or deletes a reminder, the application should update or cancel the previous scheduled event so that outdated notifications do not appear.
8. Scheduling a Reminder with AlarmManager
AlarmManager can schedule an event for a future time. A reminder application can use a PendingIntent to identify the operation that should happen when the alarm fires.
Basic Scheduling Flow
- Save the reminder and obtain a stable reminder ID.
- Create an explicit Intent targeting a BroadcastReceiver.
- Create a PendingIntent with a unique request code for that reminder.
- Choose an alarm type appropriate to the requirement.
- Schedule the alarm and handle any permission or access restrictions.
- When the alarm fires, display the reminder notification.
Example Alarm Scheduling Code
fun scheduleReminder(
context: Context,
reminderId: Int,
triggerAtMillis: Long
) {
val alarmManager = context.getSystemService(
Context.ALARM_SERVICE
) as AlarmManager
val intent = Intent(
context,
ReminderReceiver::class.java
).apply {
putExtra("reminder_id", reminderId)
}
val pendingIntent = PendingIntent.getBroadcast(
context,
reminderId,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or
PendingIntent.FLAG_IMMUTABLE
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if (!alarmManager.canScheduleExactAlarms()) {
// Explain the limitation and request access
// only when exact alarms are appropriate.
return
}
}
alarmManager.setExactAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
triggerAtMillis,
pendingIntent
)
}
Important: This is an illustrative scheduling pattern, not a complete production implementation. Add the appropriate imports, manifest declarations, Android-version checks, user-facing permission handling, timestamp validation, and rescheduling logic. Use exact alarms only when the application's functionality genuinely requires them and the applicable Android rules allow them.
9. Handling Android Permissions and Exact Alarm Access
Android versions differ in how they control notifications and exact alarms. An application must declare and request the permissions appropriate to its target SDK, distribution method, and use case.
- Notifications: Android 13 and later generally require runtime notification permission for ordinary notifications. Declare
POST_NOTIFICATIONS and request it when appropriate.
- Exact alarms: On relevant Android versions, exact-alarm access is controlled by special access rules. Depending on the use case and target SDK, the app may need
SCHEDULE_EXACT_ALARM or may qualify for USE_EXACT_ALARM.
- Permission denial: Do not assume an alarm can be scheduled precisely if access has not been granted.
- Fallback behavior: Offer an appropriate alternative, such as an inexact alarm, when exact timing is not essential.
- Permission changes: Recheck the relevant access before scheduling and handle revoked access gracefully.
Never request special access without explaining why it is necessary. The exact manifest and runtime behavior must be checked against the Android version and target SDK used by the project.
10. Creating Notification Channels
Android 8.0 and later require notifications to be associated with a notification channel. A channel defines settings such as the notification category and user-controlled importance.
Example Channel Creation
fun createReminderChannel(context: Context) {
if (Build.VERSION.SDK_INT >=
Build.VERSION_CODES.O
) {
val channel = NotificationChannel(
"reminder_channel",
"Task Reminders",
NotificationManager.IMPORTANCE_HIGH
).apply {
description =
"Notifications for scheduled reminders"
}
val manager = context.getSystemService(
NotificationManager::class.java
)
manager.createNotificationChannel(channel)
}
}
Call the channel creation function during application initialization or before posting a notification. Once a channel has been created, users can control its notification settings in Android system settings.
11. Displaying a Reminder Notification
When the alarm fires, a BroadcastReceiver can retrieve the reminder ID, load the relevant reminder data, and display a notification. Avoid performing long database or network operations directly inside a receiver; use an appropriate asynchronous mechanism when needed.
Example Notification Builder
fun showReminderNotification(
context: Context,
reminderId: Int,
title: String,
description: String
) {
val notification = NotificationCompat.Builder(
context,
"reminder_channel"
)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle(title)
.setContentText(description)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setAutoCancel(true)
.build()
NotificationManagerCompat
.from(context)
.notify(reminderId, notification)
}
Replace R.drawable.ic_notification with a valid notification icon in the project. Before posting, ensure notification permission is granted where required. If permission is denied, provide useful in-app feedback rather than assuming that the notification was displayed.
12. Implementing a BroadcastReceiver
A BroadcastReceiver receives the alarm event and initiates the notification flow. It should complete quickly and avoid blocking operations.
Example Receiver Structure
class ReminderReceiver : BroadcastReceiver() {
override fun onReceive(
context: Context,
intent: Intent
) {
val reminderId = intent.getIntExtra(
"reminder_id",
-1
)
if (reminderId == -1) return
// Load the reminder and display its
// notification using a suitable repository
// or asynchronous processing strategy.
}
}
The receiver above demonstrates the entry point only. A production implementation must retrieve the reminder's current data, verify that it remains enabled, and ensure that notification creation is compatible with the receiver's execution limits.
13. Implementing Recurring Reminders
Recurring reminders allow users to repeat tasks at regular intervals, such as every morning or every Monday. The application should define whether recurrence follows a fixed elapsed duration or a local calendar schedule.
Common Recurrence Types
| Type |
Example |
Implementation Consideration |
| Daily |
Take a planned action every day at 8 AM. |
Recalculate the next local occurrence. |
| Weekly |
Attend a meeting every Monday. |
Store the selected weekday and local time. |
| Monthly |
Pay a bill on the 5th of each month. |
Define behavior for months with fewer days. |
| Custom |
Repeat on selected weekdays. |
Store recurrence rules and calculate the next occurrence. |
For recurring calendar reminders, schedule the next occurrence after handling the current one. This approach makes it easier to account for daylight-saving changes, edits, missed alarms, and device time changes.
14. Editing and Deleting Scheduled Tasks
Users must be able to modify or cancel reminders without leaving stale alarms behind.
- Edit: Cancel the existing alarm, update the database record, and schedule the new time.
- Delete: Cancel the corresponding PendingIntent and remove the database record.
- Disable: Cancel the scheduled event while retaining the reminder details.
- Re-enable: Validate the time and schedule the reminder again.
- Duplicate prevention: Use a stable ID and matching PendingIntent identity to manage each reminder consistently.
When cancelling an alarm, recreate the PendingIntent using the same receiver, request code, and matching Intent identity used to create it. Use the same identity strategy when updating an existing alarm.
15. Handling Device Reboots and Time Changes
Applications should not assume every scheduled alarm remains available after a device restart or relevant system event. If reminders must persist across reboots, retain their definitions in the database and restore eligible schedules when the system allows it.
- Declare a receiver for the required system events when appropriate.
- Request the necessary permissions and follow Android background execution rules.
- Re-read active reminders from the database after reboot.
- Reschedule only reminders that remain valid and enabled.
- Recalculate local-calendar reminders after time or time-zone changes.
- Handle exact-alarm access changes where applicable.
- Test behavior after reboot, app update, force-stop, and permission changes.
Do not treat a force-stopped application as equivalent to a normally closed application. Android may prevent its components and scheduled behavior from running until the user launches it again.
16. Using WorkManager for Deferred Tasks
WorkManager is appropriate when a task must eventually run and can tolerate flexible execution time. Examples include syncing reminder records to a server, cleaning old completed tasks, or uploading task activity.
Example One-Time Work Request
val workRequest =
OneTimeWorkRequestBuilder<ReminderSyncWorker>()
.build()
WorkManager.getInstance(context)
.enqueue(workRequest)
This example enqueues a background task; it does not guarantee that a user-facing reminder will trigger at an exact clock time. For tasks with network or battery requirements, configure constraints and retry policies. For recurring background maintenance, consider PeriodicWorkRequest, remembering that periodic execution is not exact.
17. Managing Application State and Data Consistency
A reminder application needs to keep its database records, scheduled alarms, and displayed task list consistent.
- Use a repository to coordinate storage and scheduling operations.
- Represent enabled, completed, and cancelled states explicitly where required.
- Use stable identifiers for alarms and notifications.
- Handle database failures and scheduling failures separately.
- Refresh the user interface from persisted data instead of relying only on temporary screen state.
- Provide clear feedback when saving succeeds but scheduling cannot be completed.
Database persistence and alarm scheduling are separate operations, so they cannot automatically be treated as one atomic transaction. A robust implementation should record the desired reminder state and reconcile scheduled alarms with stored records when the application starts or receives relevant system events.
18. Error Handling and Troubleshooting
| Problem |
Possible Cause |
Solution |
| Notification does not appear |
Permission denied or channel disabled. |
Check runtime permission and channel settings. |
| Alarm triggers late |
An inexact scheduling mechanism was used. |
Choose an appropriate alarm API and verify exact-alarm access. |
| Duplicate notifications |
Multiple alarms or reused notification IDs. |
Use stable reminder IDs and cancel outdated schedules. |
| Reminder disappears after reboot |
Schedules were not restored. |
Reload active records and reschedule them when permitted. |
| Edited reminder triggers at old time |
The original alarm was not cancelled. |
Cancel the old PendingIntent and create the updated schedule. |
| Crash during notification creation |
Invalid icon or missing setup. |
Check resources, channel creation, and permission handling. |
| Recurring reminder shifts unexpectedly |
Fixed intervals were used for a local calendar event. |
Calculate the next occurrence using the intended calendar and time zone. |
19. Testing the Reminder Application
Testing should cover the full reminder lifecycle, including scheduling, notification display, editing, cancellation, and recovery after system changes.
Functional Test Checklist
- Create a reminder with a valid title, date, and time.
- Verify that invalid input produces a helpful error.
- Confirm that a due reminder produces the expected notification when permissions allow.
- Edit the reminder and confirm the previous schedule is cancelled.
- Delete a reminder and verify that it no longer triggers.
- Test notification permission denial and later permission approval.
- Test exact-alarm access behavior on supported Android versions.
- Test recurring reminders across date boundaries and time-zone changes.
- Restart the device and verify restoration behavior where supported.
- Test the application on multiple Android versions and screen sizes.
Recommended Test Types
- Unit tests: Validate date calculations, recurrence rules, and input validation.
- Repository tests: Verify database operations and reminder state changes.
- Integration tests: Verify interactions between storage and scheduling components.
- UI tests: Test reminder creation, editing, deletion, and permission prompts.
- Device tests: Check actual alarm and notification behavior on physical devices.
20. Security, Privacy, and Accessibility
- Request only the permissions required by the application's features.
- Store reminder data locally unless synchronization is needed.
- Avoid exposing private reminder content in notifications on a locked device.
- Allow users to control notification settings and delete their stored reminders.
- Use meaningful labels and accessible touch targets.
- Support readable text, adequate contrast, and screen readers.
- Do not include sensitive details in logs or analytics without a justified and privacy-conscious design.
21. Example Project: Daily Task Reminder App
Build a small Android application that lets users schedule personal tasks and receive notifications when those tasks are due.
Core Features
- Display a list of saved reminders.
- Add a reminder with a title, description, date, and time.
- Persist reminder details in a Room database.
- Schedule the alarm using an appropriate Android API.
- Display a notification when the reminder is due.
- Edit, disable, complete, and delete reminders.
- Support recurring reminders as an optional enhancement.
- Restore eligible schedules after reboot when required.
Suggested Development Workflow
- Create the Android project using Kotlin.
- Design the reminder list and creation screen.
- Define the reminder entity, DAO, database, and repository.
- Implement date and time validation.
- Create the notification channel and permission flow.
- Implement alarm scheduling and cancellation.
- Connect the UI to a ViewModel and repository.
- Test notification behavior and recovery scenarios.
- Improve accessibility, error messages, and overall reliability.
22. Best Practices
- Choose AlarmManager for time-sensitive reminders and WorkManager for deferrable background tasks.
- Persist reminder definitions rather than relying on scheduled events alone.
- Use stable IDs for database records, PendingIntents, and notifications.
- Check applicable Android permissions and special access requirements.
- Explain permission requests in context and handle denial gracefully.
- Use notification channels and respect user notification preferences.
- Recalculate calendar-based reminders when relevant date or time settings change.
- Cancel obsolete alarms whenever reminders are edited or deleted.
- Keep receivers lightweight and avoid lengthy synchronous operations.
- Test on real devices because background and alarm behavior can vary by Android version and manufacturer.
23. Practical Exercises
- Create a reminder form with title, date, and time fields.
- Store reminders in a Room database and display them in a list.
- Schedule an alarm for a selected future time.
- Create a notification channel and show a reminder notification.
- Add edit and delete functionality with proper alarm cancellation.
- Implement a daily recurring reminder based on local time.
- Add reboot recovery and test the restoration of active reminders.
- Display helpful messages when notification or exact-alarm access is unavailable.
24. Interview Questions and Answers
Q1. What is a reminder application in Android?
A reminder application allows users to create tasks and receive alerts at selected dates and times. It commonly uses local storage, scheduling APIs, and notifications.
Q2. What is the difference between AlarmManager and WorkManager?
AlarmManager supports time-based alarms, including exact alarms when permitted. WorkManager is designed for persistent, deferrable background work that should run when system conditions allow.
Q3. Why should reminders be saved in a database?
A database preserves reminder definitions after the application process ends. It also makes it possible to restore schedules, display saved tasks, and support editing or deletion.
Q4. What is a PendingIntent?
A PendingIntent allows the Android system to execute a predefined operation on behalf of an application at a later time, subject to system rules.
Q5. Why are notification channels necessary?
Android 8.0 and later use notification channels to group notifications and let users manage their importance and behavior.
Q6. How can duplicate alarms be prevented?
Use a consistent PendingIntent identity and stable request codes. Update or cancel existing alarms before creating replacement schedules.
Q7. How should a reminder app handle device reboots?
Persist reminder definitions and restore eligible schedules after reboot using the required receiver declarations, permissions, and Android-compatible behavior.
Q8. What should happen if notification permission is denied?
The application should explain that notifications are disabled, allow the user to review settings if desired, and avoid falsely claiming that an alert was delivered.
Q9. How should recurring reminders handle time zones?
The application should define whether recurrence follows local calendar time or a fixed elapsed duration. Local-time reminders should recalculate future occurrences when time-zone or daylight-saving rules change.
Q10. How can a reminder application be made reliable?
Combine persistent storage, suitable scheduling APIs, stable identifiers, permission handling, cancellation and recovery logic, clear error states, and thorough device testing.
25. Quick Revision
- A reminder application combines a user interface, persistent storage, scheduling, and notifications.
- AlarmManager is appropriate for user-facing alarms that need a particular trigger time.
- WorkManager is intended for persistent background work that can be deferred.
- Room can store reminder details and support recovery after process termination.
- Notification channels and applicable runtime permissions must be handled correctly.
- Editing or deleting reminders requires cancellation or replacement of associated alarms.
- Recurring reminders need explicit calendar and time-zone rules.
- Device reboot and system permission changes require deliberate recovery behavior.
Conclusion
Building a reminder or scheduled task application is a practical Android development project that brings together Kotlin, UI design, database management, background scheduling, and notification handling. The most important design decision is choosing the right scheduling mechanism for the required timing guarantees. By persisting reminder data, handling Android permissions correctly, cancelling obsolete alarms, and testing real-device behavior, developers can build a reminder application that is maintainable, user-friendly, and dependable.
Explore Android App Development with JustAcademy
Learn more about Android development, app architecture, Kotlin programming, notifications, and background task management through the following resources.
Course: Explore the Android App Development Course
Course Demo: Register for a Course Demo
Official Website: Visit JustAcademy