Skip to content

fix: prevent StateError crash in getTriggeredAlarm lookup (#906)#923

Open
Varvyju wants to merge 1 commit intoCCExtractor:mainfrom
Varvyju:fix/get-triggered-alarm-crash
Open

fix: prevent StateError crash in getTriggeredAlarm lookup (#906)#923
Varvyju wants to merge 1 commit intoCCExtractor:mainfrom
Varvyju:fix/get-triggered-alarm-crash

Conversation

@Varvyju
Copy link
Copy Markdown

@Varvyju Varvyju commented Mar 31, 2026

Summary

Fixes Issue #906.

Prevents a fatal StateError crash when getTriggeredAlarm is called and no enabled alarms match the current time.

Changes Made

  • ISAR Provider: Replaced the unoptimized .findAll() query with .findFirst() for better memory and IO performance. Added a null-safety guard to return null gracefully instead of throwing a StateError.
  • Firestore Provider: Applied the same empty-result guard, adding .limit(1) to optimize the document read query, and properly returning null if the snapshot is empty.
  • Scope: Unlike previous attempts at this issue (e.g., PR fix: handle empty triggered alarm lookup #907), this fix is strictly scoped to the database queries, handles both ISAR and Firestore parity, and does not introduce unrelated UI changes.

Testing Done

  • Verified flutter analyze passes with no errors.
  • Confirmed that empty query results now resolve safely without crashing the background service.

Copilot AI review requested due to automatic review settings March 31, 2026 18:40
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a crash in triggered-alarm lookup by making both ISAR and Firestore implementations return null when no enabled alarm matches the requested time, and by reducing query overhead.

Changes:

  • ISAR: switch from findAll() to findFirst() and return null instead of throwing on empty results.
  • Firestore: add limit(1), guard empty snapshots, and return null instead of indexing into an empty list.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 7 comments.

File Description
lib/app/data/providers/isar_provider.dart Makes triggered-alarm query nullable and more efficient via findFirst() + empty-result guard.
lib/app/data/providers/firestore_provider.dart Adds empty-result guard and query optimization via limit(1), returning nullable results.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

.findFirst();

if (alarm == null) {
debugPrint('No enabled alarm found in ISAR for time: $time');
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logging a debugPrint every time no alarm matches can get noisy if this lookup runs frequently (e.g., background polling). Consider either removing this log, downgrading it behind a debug flag, or rate-limiting it to avoid log spam in normal operation.

Suggested change
debugPrint('No enabled alarm found in ISAR for time: $time');
if (kDebugMode) {
debugPrint('No enabled alarm found in ISAR for time: $time');
}

Copilot uses AI. Check for mistakes.
Comment on lines +323 to +336
// static Future<AlarmModel> getTriggeredAlarm(String time) async {
// final isarProvider = IsarDb();
// final db = await isarProvider.db;

// final alarms = await db.alarmModels
// .where()
// .filter()
// .isEnabledEqualTo(true)
// .and()
// .alarmTimeEqualTo(time)
// .findAll();
// return alarms.first;
// }

Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The previous implementation of getTriggeredAlarm is left in the file as commented-out code. Since it’s already available in git history, please remove the dead commented-out block to reduce clutter and avoid future confusion.

Suggested change
// static Future<AlarmModel> getTriggeredAlarm(String time) async {
// final isarProvider = IsarDb();
// final db = await isarProvider.db;
// final alarms = await db.alarmModels
// .where()
// .filter()
// .isEnabledEqualTo(true)
// .and()
// .alarmTimeEqualTo(time)
// .findAll();
// return alarms.first;
// }

Copilot uses AI. Check for mistakes.
Comment on lines +337 to 357
static Future<AlarmModel?> getTriggeredAlarm(String time) async {
final isarProvider = IsarDb();
final db = await isarProvider.db;

// Use findFirst() instead of findAll() to save memory and IO
final alarm = await db.alarmModels
.where()
.filter()
.isEnabledEqualTo(true)
.and()
.alarmTimeEqualTo(time)
.findFirst();

if (alarm == null) {
debugPrint('No enabled alarm found in ISAR for time: $time');
return null; // Return null instead of throwing an error to prevent crashes
}

return alarm;
}

Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getTriggeredAlarm is not indented consistently with the rest of the IsarDb class (it starts at column 0). Please run dart format / adjust indentation so the method aligns with surrounding static methods, improving readability and keeping style consistent.

Suggested change
static Future<AlarmModel?> getTriggeredAlarm(String time) async {
final isarProvider = IsarDb();
final db = await isarProvider.db;
// Use findFirst() instead of findAll() to save memory and IO
final alarm = await db.alarmModels
.where()
.filter()
.isEnabledEqualTo(true)
.and()
.alarmTimeEqualTo(time)
.findFirst();
if (alarm == null) {
debugPrint('No enabled alarm found in ISAR for time: $time');
return null; // Return null instead of throwing an error to prevent crashes
}
return alarm;
}
static Future<AlarmModel?> getTriggeredAlarm(String time) async {
final isarProvider = IsarDb();
final db = await isarProvider.db;
// Use findFirst() instead of findAll() to save memory and IO
final alarm = await db.alarmModels
.where()
.filter()
.isEnabledEqualTo(true)
.and()
.alarmTimeEqualTo(time)
.findFirst();
if (alarm == null) {
debugPrint('No enabled alarm found in ISAR for time: $time');
return null; // Return null instead of throwing an error to prevent crashes
}
return alarm;
}

Copilot uses AI. Check for mistakes.
.findFirst();

if (alarm == null) {
debugPrint('No enabled alarm found in ISAR for time: $time');
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logging a debugPrint every time no alarm matches can get noisy if this lookup runs frequently (e.g., background polling). Consider removing this log, guarding it behind a debug-only flag, or rate-limiting it to avoid log spam in normal operation.

Suggested change
debugPrint('No enabled alarm found in ISAR for time: $time');
assert(() {
debugPrint('No enabled alarm found in ISAR for time: $time');
return true;
}());

Copilot uses AI. Check for mistakes.
Comment on lines +154 to +172
// static Future<AlarmModel> getTriggeredAlarm(
// UserModel? user,
// String time,
// ) async {
// HomeController homeController = Get.find<HomeController>();
// if (user == null) return homeController.genFakeAlarmModel();
// QuerySnapshot snapshot = await _alarmsCollection(user)
// .where('isEnabled', isEqualTo: true)
// .where('alarmTime', isEqualTo: time)
// .get();

// List list = snapshot.docs.map((DocumentSnapshot document) {
// return AlarmModel.fromDocumentSnapshot(
// documentSnapshot: document,
// user: user,
// );
// }).toList();

// return list[0];
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The previous getTriggeredAlarm implementation is kept as commented-out code. Please remove the dead commented-out block (git history preserves it) to reduce noise and maintenance overhead.

Suggested change
// static Future<AlarmModel> getTriggeredAlarm(
// UserModel? user,
// String time,
// ) async {
// HomeController homeController = Get.find<HomeController>();
// if (user == null) return homeController.genFakeAlarmModel();
// QuerySnapshot snapshot = await _alarmsCollection(user)
// .where('isEnabled', isEqualTo: true)
// .where('alarmTime', isEqualTo: time)
// .get();
// List list = snapshot.docs.map((DocumentSnapshot document) {
// return AlarmModel.fromDocumentSnapshot(
// documentSnapshot: document,
// user: user,
// );
// }).toList();
// return list[0];

Copilot uses AI. Check for mistakes.
Comment on lines +175 to +180
static Future<AlarmModel?> getTriggeredAlarm(UserModel? user, String time) async {
try {
// Use their built-in _alarmsCollection function to get the correct path
QuerySnapshot querySnapshot = await _alarmsCollection(user)
.where('isEnabled', isEqualTo: true)
.where('alarmTime', isEqualTo: time)
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change removes the prior user == null guard (it used to return a fake model). As written, the method will query _alarmsCollection(null) (which points at the top-level alarms collection) and then build an AlarmModel with user: null. Please add an explicit user == null early return (likely null) to avoid unintended/unauthorized reads and to keep null-user behavior consistent with other methods in this provider (e.g., getLatestAlarm, getAlarm, addAlarm).

Copilot uses AI. Check for mistakes.
Comment on lines +193 to +195
} catch (e) {
debugPrint('Error fetching triggered alarm from Firestore: $e');
return null;
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Catching a broad catch (e) and returning null will also swallow unexpected non-Firestore errors (e.g., mapping/logic bugs), making defects harder to detect. Consider catching FirebaseException (or a narrower set of expected exceptions) and rethrowing unexpected exceptions after logging so real errors aren’t silently masked.

Suggested change
} catch (e) {
debugPrint('Error fetching triggered alarm from Firestore: $e');
return null;
} on FirebaseException catch (e) {
debugPrint('Error fetching triggered alarm from Firestore: $e');
return null;
} catch (e, stackTrace) {
debugPrint('Unexpected error fetching triggered alarm from Firestore: $e');
debugPrint('Stack trace: $stackTrace');
rethrow;

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants