Fix/snoozed alarm sound#15
Conversation
- Added BootBroadcastReceiver to handle BOOT_COMPLETED intent - Added RECEIVE_BOOT_COMPLETED permission to AndroidManifest.xml - Automatically reschedules all active alarms from database on boot - Fixes issue CCExtractor#9
- Implemented dynamic location fetching using fl_location package - Added fetchCurrentLocation() method with permission handling - Replaced hardcoded New Delhi coordinates with real-time user location - Added fallback to default location if permission denied or unavailable - Added loading indicator in UI while fetching location - Graceful error handling with user-friendly snackbar notifications - Fixes issue CCExtractor#6
There was a problem hiding this comment.
Pull request overview
Fixes a bug where snoozed alarms don’t replay sound/vibration by ensuring alarm playback resources are fully cleaned up before rescheduling; additionally introduces boot-time alarm rescheduling and location-condition UX changes.
Changes:
- Nulls out
AlarmServiceHolder.ringtone/vibratorafter stopping/cancelling to force fresh instances on the next trigger. - Adds a boot-complete broadcast receiver and manifest permission/registration to reschedule alarms after reboot.
- Updates location-condition flow to fetch current location with permission/service checks and show a loading indicator.
Reviewed changes
Copilot reviewed 5 out of 6 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| pubspec.lock | Updates resolved Dart/Flutter dependency versions and recorded SDK resolution constraints. |
| lib/app/modules/smart_control/views/location/location_condition_view.dart | Disables taps during location fetch and shows a progress indicator for the selected option. |
| lib/app/modules/smart_control/controllers/location_controller.dart | Adds current-location fetching with permission/service handling and sets picker start location accordingly. |
| android/app/src/main/kotlin/com/example/uac_companion/AlarmServices/BroadcastReceivers/BootBroadcastReceiver.kt | Adds a boot receiver that reschedules alarms after device reboot. |
| android/app/src/main/kotlin/com/example/uac_companion/AlarmServices/BroadcastReceivers/AlarmSnoozeReceiver.kt | Stops and clears ringtone/vibrator instances when snoozing to avoid reusing stopped objects. |
| android/app/src/main/AndroidManifest.xml | Adds RECEIVE_BOOT_COMPLETED permission and registers the new boot receiver. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| android:exported="true"> | ||
| <intent-filter> | ||
| <action android:name="android.intent.action.BOOT_COMPLETED" /> | ||
| <action android:name="android.intent.action.QUICKBOOT_POWERON" /> |
There was a problem hiding this comment.
The manifest registers QUICKBOOT_POWERON, but BootBroadcastReceiver currently only handles ACTION_BOOT_COMPLETED. Either add handling for QUICKBOOT_POWERON in the receiver or remove this <action> to avoid a dead/unused intent-filter entry.
| <action android:name="android.intent.action.QUICKBOOT_POWERON" /> |
|
|
||
| try { | ||
| // Get all enabled alarms from the database | ||
| val alarms = AlarmUtils.getAllAlarmsFromDb(context) | ||
| val enabledAlarms = alarms.filter { it.isEnabled == 1 } | ||
|
|
||
| Log.d(TAG, "Found ${enabledAlarms.size} enabled alarms to reschedule") | ||
|
|
||
| // Reschedule each alarm using the AlarmScheduler | ||
| // The scheduler will automatically pick the next upcoming alarm | ||
| if (enabledAlarms.isNotEmpty()) { | ||
| AlarmScheduler.scheduleNextAlarm(context) | ||
| Log.d(TAG, "Successfully rescheduled alarms after boot") | ||
| } else { | ||
| Log.d(TAG, "No active alarms to reschedule") | ||
| } |
There was a problem hiding this comment.
AlarmScheduler.scheduleNextAlarm(context) already reads enabled alarms from the DB. The extra getAllAlarmsFromDb() + filter { it.isEnabled == 1 } here duplicates work and increases boot-time overhead; consider calling the scheduler directly (or refactor the scheduler to accept the pre-fetched list).
| try { | |
| // Get all enabled alarms from the database | |
| val alarms = AlarmUtils.getAllAlarmsFromDb(context) | |
| val enabledAlarms = alarms.filter { it.isEnabled == 1 } | |
| Log.d(TAG, "Found ${enabledAlarms.size} enabled alarms to reschedule") | |
| // Reschedule each alarm using the AlarmScheduler | |
| // The scheduler will automatically pick the next upcoming alarm | |
| if (enabledAlarms.isNotEmpty()) { | |
| AlarmScheduler.scheduleNextAlarm(context) | |
| Log.d(TAG, "Successfully rescheduled alarms after boot") | |
| } else { | |
| Log.d(TAG, "No active alarms to reschedule") | |
| } | |
| try { | |
| // Delegate to AlarmScheduler, which reads enabled alarms from the database | |
| AlarmScheduler.scheduleNextAlarm(context) | |
| Log.d(TAG, "AlarmScheduler invoked to reschedule alarms after boot") |
|
|
||
| try { | ||
| // Get all enabled alarms from the database | ||
| val alarms = AlarmUtils.getAllAlarmsFromDb(context) | ||
| val enabledAlarms = alarms.filter { it.isEnabled == 1 } | ||
|
|
||
| Log.d(TAG, "Found ${enabledAlarms.size} enabled alarms to reschedule") | ||
|
|
||
| // Reschedule each alarm using the AlarmScheduler | ||
| // The scheduler will automatically pick the next upcoming alarm | ||
| if (enabledAlarms.isNotEmpty()) { | ||
| AlarmScheduler.scheduleNextAlarm(context) | ||
| Log.d(TAG, "Successfully rescheduled alarms after boot") | ||
| } else { | ||
| Log.d(TAG, "No active alarms to reschedule") | ||
| } | ||
| } catch (e: Exception) { | ||
| Log.e(TAG, "Error rescheduling alarms after boot: ${e.message}", e) | ||
| } |
There was a problem hiding this comment.
This receiver does synchronous DB IO inside onReceive. If the alarms table grows, this can risk slow boot handling / ANR. Consider using goAsync() + background work (or scheduling a WorkManager job) and returning quickly from onReceive.
| try { | |
| // Get all enabled alarms from the database | |
| val alarms = AlarmUtils.getAllAlarmsFromDb(context) | |
| val enabledAlarms = alarms.filter { it.isEnabled == 1 } | |
| Log.d(TAG, "Found ${enabledAlarms.size} enabled alarms to reschedule") | |
| // Reschedule each alarm using the AlarmScheduler | |
| // The scheduler will automatically pick the next upcoming alarm | |
| if (enabledAlarms.isNotEmpty()) { | |
| AlarmScheduler.scheduleNextAlarm(context) | |
| Log.d(TAG, "Successfully rescheduled alarms after boot") | |
| } else { | |
| Log.d(TAG, "No active alarms to reschedule") | |
| } | |
| } catch (e: Exception) { | |
| Log.e(TAG, "Error rescheduling alarms after boot: ${e.message}", e) | |
| } | |
| // Use goAsync to offload potentially long-running DB work off the main thread | |
| val pendingResult = goAsync() | |
| Thread { | |
| try { | |
| // Get all enabled alarms from the database | |
| val alarms = AlarmUtils.getAllAlarmsFromDb(context) | |
| val enabledAlarms = alarms.filter { it.isEnabled == 1 } | |
| Log.d(TAG, "Found ${enabledAlarms.size} enabled alarms to reschedule") | |
| // Reschedule each alarm using the AlarmScheduler | |
| // The scheduler will automatically pick the next upcoming alarm | |
| if (enabledAlarms.isNotEmpty()) { | |
| AlarmScheduler.scheduleNextAlarm(context) | |
| Log.d(TAG, "Successfully rescheduled alarms after boot") | |
| } else { | |
| Log.d(TAG, "No active alarms to reschedule") | |
| } | |
| } catch (e: Exception) { | |
| Log.e(TAG, "Error rescheduling alarms after boot: ${e.message}", e) | |
| } finally { | |
| pendingResult.finish() | |
| } | |
| }.start() |
| currentUserLocation.value = fallbackLatLng; | ||
| isLoadingLocation.value = false; | ||
| return; | ||
| } |
There was a problem hiding this comment.
These early returns manually set isLoadingLocation to false, but the finally block also sets it to false even when returning. Removing the intermediate assignments avoids duplication and makes the loading state handling less error-prone.
| // Fetch current location before opening picker | ||
| await fetchCurrentLocation(); | ||
|
|
||
| // Use current user location or fallback | ||
| pickerLatLng.value = currentUserLocation.value ?? fallbackLatLng; |
There was a problem hiding this comment.
The PR description/title are about fixing snoozed alarm sound, but this hunk adds location fetching/permission flow before opening the picker. If this isn’t required for the alarm fix, consider splitting these location changes into a separate PR to reduce review and revert risk.
|
Looks good overall However, this PR includes 3 commits addressing 3 different issues. It would be much clearer and easier to review if each issue was handled in a separate branch and raised as separate PRs. Also, going forward, please attach a short demo video with each PR showing the final working state. For example, for the “replace hardcoded location with user’s current location” change, include a small video demonstrating that functionality working properly. |
Description
Fixed snoozed alarm sound not playing. The issue was that the ringtone and vibrator instances were being stopped but not set to null in AlarmSnoozeReceiver, causing the snoozed alarm to reuse the stopped instances instead of creating fresh ones.
Type of Change
Proposed Changes
AlarmSnoozeReceiver.ktto properly clean up ringtone and vibrator instancesAlarmServiceHolder.ringtoneandAlarmServiceHolder.vibratorafter stopping themFixes #4
Screenshots
N/A - Audio/notification fix without UI changes
Checklist