feat: add configurable trade history retention in settings#571
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds a nullable Settings.sessionExpirationHours persisted to prefs, UI for trade-history retention, notifier method to update it, session init/cleanup to respect the setting (0 = never) with cascade deletion of session-related data, localization keys, tests, and documentation updates. Changes
Sequence DiagramsequenceDiagram
participant User as User
participant UI as SettingsScreen
participant Notifier as SettingsNotifier
participant Model as SettingsModel
participant Session as SessionNotifier
participant Config as Config
User->>UI: open trade history settings
UI->>UI: show dropdown of presets
User->>UI: select retention hours
UI->>Notifier: updateSessionExpirationHours(newValue)
Notifier->>Model: copyWith(sessionExpirationHours: newValue)
Notifier->>Notifier: _saveToPrefs()
Note over Session,Model: On init or periodic cleanup
Session->>Model: read sessionExpirationHours
alt sessionExpirationHours is null
Session->>Config: use Config.sessionExpirationHours
else sessionExpirationHours == 0
Session->>Session: skip cleanup (forever)
else
Session->>Session: compute cutoff and delete expired sessions + cascade cleanup
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
lib/features/settings/settings_notifier.dart (1)
208-213: GuardsessionExpirationHoursagainst invalid values before persisting.This setter accepts any integer. Invalid values (especially negatives) can break retention semantics in downstream cutoff calculations. Consider restricting to known presets plus
0(“never”).Proposed hardening
Future<void> updateSessionExpirationHours(int newValue) async { + const allowedValues = <int>{0, 168, 720, 2160, 4320, 8760}; + if (!allowedValues.contains(newValue)) { + logger.w('Ignoring invalid session expiration value: $newValue'); + return; + } + final oldValue = state.sessionExpirationHours; + if (oldValue == newValue) return; + state = state.copyWith(sessionExpirationHours: newValue); await _saveToPrefs(); logger.i('Session expiration changed: $oldValue → $newValue hours'); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/features/settings/settings_notifier.dart` around lines 208 - 213, The updateSessionExpirationHours setter currently accepts any int; validate the incoming newValue in updateSessionExpirationHours before mutating state and calling _saveToPrefs: ensure newValue is one of the allowed presets (e.g., 0 for “never” and the defined retention options) and reject or clamp negatives/unknown values (log/warn and return early or set to a safe default). Use state.sessionExpirationHours and the same copyWith/state update pattern only after validation, and keep the existing logger.i call to record the effective change.lib/shared/notifiers/session_notifier.dart (1)
90-93: Avoid running periodic no-op cleanup when retention is set to “Never”.
_cleanup()now exits early for_isForever, but the periodic timer still triggers. Consider skipping scheduling in that mode and re-scheduling when settings change.♻️ Suggested refactor
void _scheduleCleanup() { _cleanupTimer?.cancel(); + if (_isForever) return; _cleanupTimer = Timer.periodic( const Duration(minutes: Config.cleanupIntervalMinutes), (timer) => _cleanup(), ); } void updateSettings(Settings settings) { _settings = settings.copyWith(); + _scheduleCleanup(); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/shared/notifiers/session_notifier.dart` around lines 90 - 93, The periodic cleanup timer is still scheduled even when _cleanup() immediately returns for _isForever; update the scheduling logic to skip creating the timer when _isForever is true and to cancel any existing timer when settings flip to forever. Locate the timer creation/management code (e.g., _startCleanupTimer, _schedulePeriodicCleanup or the constructor where a Timer/Timer.periodic is created) and add a guard that only schedules the periodic Timer when !_isForever; also ensure you cancel and null out the timer (e.g., _cleanupTimer?.cancel(); _cleanupTimer = null) when _isForever becomes true and reschedule the timer when settings change back to finite retention.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@lib/features/settings/settings_notifier.dart`:
- Around line 208-213: The updateSessionExpirationHours setter currently accepts
any int; validate the incoming newValue in updateSessionExpirationHours before
mutating state and calling _saveToPrefs: ensure newValue is one of the allowed
presets (e.g., 0 for “never” and the defined retention options) and reject or
clamp negatives/unknown values (log/warn and return early or set to a safe
default). Use state.sessionExpirationHours and the same copyWith/state update
pattern only after validation, and keep the existing logger.i call to record the
effective change.
In `@lib/shared/notifiers/session_notifier.dart`:
- Around line 90-93: The periodic cleanup timer is still scheduled even when
_cleanup() immediately returns for _isForever; update the scheduling logic to
skip creating the timer when _isForever is true and to cancel any existing timer
when settings flip to forever. Locate the timer creation/management code (e.g.,
_startCleanupTimer, _schedulePeriodicCleanup or the constructor where a
Timer/Timer.periodic is created) and add a guard that only schedules the
periodic Timer when !_isForever; also ensure you cancel and null out the timer
(e.g., _cleanupTimer?.cancel(); _cleanupTimer = null) when _isForever becomes
true and reschedule the timer when settings change back to finite retention.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: aafa8c9e-88ef-4db8-bfa6-5a05432a0d16
📒 Files selected for processing (10)
lib/features/settings/settings.dartlib/features/settings/settings_notifier.dartlib/features/settings/settings_screen.dartlib/l10n/intl_de.arblib/l10n/intl_en.arblib/l10n/intl_es.arblib/l10n/intl_fr.arblib/l10n/intl_it.arblib/shared/notifiers/session_notifier.dartlib/shared/providers/app_init_provider.dart
There was a problem hiding this comment.
🧹 Nitpick comments (1)
lib/shared/widgets/trade_history_selector.dart (1)
28-29: Avoid hardcoding720for dropdown fallback.At Line 29, the literal
720can drift fromConfig.sessionExpirationHours. Use a config-derived default (and guard that it exists in_optionKeys) to keep UI behavior aligned with runtime config.♻️ Suggested refactor
- final displayValue = - _optionKeys.containsKey(effectiveValue) ? effectiveValue : 720; + final defaultValue = _optionKeys.containsKey(Config.sessionExpirationHours) + ? Config.sessionExpirationHours + : _optionKeys.keys.first; + final displayValue = + _optionKeys.containsKey(effectiveValue) ? effectiveValue : defaultValue;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/shared/widgets/trade_history_selector.dart` around lines 28 - 29, The dropdown fallback currently hardcodes 720 in the assignment to displayValue which can drift from Config.sessionExpirationHours; update the logic that computes displayValue (the variable named displayValue in trade_history_selector.dart) to derive the default from Config.sessionExpirationHours and only use it if that value exists in _optionKeys (otherwise fall back to a safe existing key from _optionKeys), i.e. replace the literal 720 with a config-derived default and a membership guard against _optionKeys so the shown value always matches available options.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@lib/shared/widgets/trade_history_selector.dart`:
- Around line 28-29: The dropdown fallback currently hardcodes 720 in the
assignment to displayValue which can drift from Config.sessionExpirationHours;
update the logic that computes displayValue (the variable named displayValue in
trade_history_selector.dart) to derive the default from
Config.sessionExpirationHours and only use it if that value exists in
_optionKeys (otherwise fall back to a safe existing key from _optionKeys), i.e.
replace the literal 720 with a config-derived default and a membership guard
against _optionKeys so the shown value always matches available options.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 0660c515-d0e1-4571-bb9d-51d18803cf1a
📒 Files selected for processing (2)
lib/shared/widgets/trade_history_selector.darttest/features/settings/session_expiration_test.dart
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@lib/shared/notifiers/session_notifier.dart`:
- Around line 57-77: The _cleanupSessionData method currently force-unwraps
Session.orderId which can be null; update _cleanupSessionData to guard early
(e.g., if session.orderId == null) and return without performing cascade deletes
when orderId is null, so eventStore.deleteWhere,
mostroStore.deleteAllMessagesByOrderId, notificationsStore.deleteWhere and
SharedPreferences.remove are only called with a non-null orderId; keep
references to the same symbols (_cleanupSessionData, Session.orderId,
eventStore, mostroStore, notificationsStore, prefs) so the change is localized
and safe.
- Around line 125-129: In _cleanup() the code force-unwraps session.orderId when
calling _storage.deleteSession and _sessions.remove; change to first capture
final orderId = session.orderId and if orderId == null continue (skip this
session) before calling _storage.deleteSession(orderId) and
_sessions.remove(orderId), preserving the call to _cleanupSessionData(session)
only if you intend to run it for sessions with a valid orderId (or similarly
guard that call), so replace direct uses of session.orderId! with a null-checked
local variable to avoid force-unwrapping.
- Around line 79-97: In init(), avoid force-unwrapping session.orderId; before
using it in both branches filter or guard sessions with null orderId (e.g., skip
if session.orderId == null) and only then assign to _sessions[session.orderId!]
or call _storage.deleteSession(session.orderId!) and
_cleanupSessionData(session); ensure the _cleanupSessionData call is also
executed only when session.orderId is non-null so all occurrences in init() (the
loops and deletions) are null-safe and consistent with other guards in the
class.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 0725e2a2-c206-4812-826e-e41d29875fcf
📒 Files selected for processing (4)
docs/architecture/APP_INITIALIZATION_ANALYSIS.mddocs/architecture/SESSION_AND_KEY_MANAGEMENT.mddocs/architecture/TIMEOUT_DETECTION_AND_SESSION_CLEANUP.mdlib/shared/notifiers/session_notifier.dart
There was a problem hiding this comment.
♻️ Duplicate comments (2)
lib/shared/notifiers/session_notifier.dart (2)
129-134:⚠️ Potential issue | 🟡 MinorSame null-safety concern in
_cleanup().Lines 131 and 132 force-unwrap
session.orderId!without null guards, consistent with the unresolved concern ininit().🛡️ Proposed fix
for (final session in expiredSessions) { + if (session.orderId == null) continue; if (session.startTime.isBefore(cutoff)) { await _storage.deleteSession(session.orderId!); _sessions.remove(session.orderId!); await _cleanupSessionData(session); } },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/shared/notifiers/session_notifier.dart` around lines 129 - 134, The loop in _cleanup() force-unwraps session.orderId (used in _storage.deleteSession and _sessions.remove and passed to _cleanupSessionData) which can be null; guard against this by capturing session.orderId into a local (e.g., orderId), check for null and skip that session (or handle it explicitly) before calling _storage.deleteSession(orderId), _sessions.remove(orderId), and _cleanupSessionData(session), mirroring the null-safety fix you applied in init().
85-100:⚠️ Potential issue | 🟡 MinorNull-safety guards still missing in
init().Lines 87, 94, 96, and 97 force-unwrap
session.orderId!without first checking for null. While_cleanupSessionDatanow guards against null, theinit()loops do not, which can cause runtime exceptions if a malformed session exists in storage.🛡️ Proposed defensive approach
if (_isForever) { for (final session in allSessions) { + if (session.orderId == null) continue; _sessions[session.orderId!] = session; } } else { final cutoff = DateTime.now() .subtract(Duration(hours: _expirationHours)); for (final session in allSessions) { + if (session.orderId == null) continue; if (session.startTime.isAfter(cutoff)) { _sessions[session.orderId!] = session; } else { await _storage.deleteSession(session.orderId!); _sessions.remove(session.orderId!); await _cleanupSessionData(session); } } },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/shared/notifiers/session_notifier.dart` around lines 85 - 100, The init() loops currently force-unwrap session.orderId! (in the _isForever branch and the else branch) which can throw for malformed sessions; update both loops in init() to null-check session.orderId before using it (skip entries where orderId is null), only assign to _sessions[orderId] and call _storage.deleteSession(orderId) / _cleanupSessionData(session) when orderId != null, and ensure you do not assume orderId downstream when interacting with the _sessions map or calling _storage.deleteSession and _cleanupSessionData.
🧹 Nitpick comments (2)
lib/shared/widgets/trade_history_selector.dart (1)
8-83: Well-structured widget with proper value resolution and localization.The fallback logic correctly handles null preferences, invalid stored values, and uses
S.of(context)!for all user-facing strings. One minor optimization:The
Iconwidget (lines 56-60) has all constant parameters and could be markedconst:♻️ Suggested improvement
- Icon( - Icons.schedule, - color: AppTheme.mostroGreen, - size: 20, - ), + const Icon( + Icons.schedule, + color: AppTheme.mostroGreen, + size: 20, + ),,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/shared/widgets/trade_history_selector.dart` around lines 8 - 83, Summary: Make the Icon in TradeHistorySelector build constant to improve performance. Change the non-const Icon(...) instance inside the DropdownMenuItem builder (the Icon using Icons.schedule, AppTheme.mostroGreen, size: 20) to a const Icon so the widget becomes compile-time constant; ensure the surrounding Row children that include const SizedBox and Text remain valid (you may need to adjust const placement on the Text if localization prevents const). Update the Icon instantiation in the _optionKeys.map builder accordingly.lib/shared/notifiers/session_notifier.dart (1)
63-66: Type cast bypasses the repository abstraction.The cast
as NotificationsStoragecouples this code to the concrete implementation rather than theNotificationsRepositoryinterface. If the provider's backing implementation changes, this will fail at runtime.Consider adding
deleteWhereto theNotificationsRepositoryinterface, or exposing a dedicated method likedeleteByOrderId(String orderId)that encapsulates this logic.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/shared/notifiers/session_notifier.dart` around lines 63 - 66, The code in session_notifier.dart reads notificationsRepositoryProvider and force-casts it to NotificationsStorage (notificationsStore = ref.read(... ) as NotificationsStorage), which breaks the NotificationsRepository abstraction and can fail if implementation changes; update the NotificationsRepository interface to include the needed deletion operation (e.g., add deleteWhere or deleteByOrderId(String orderId)) and replace the cast with the interface type (NotificationsRepository notificationsRepo) then call the new repository method (e.g., notificationsRepo.deleteByOrderId(orderId) or notificationsRepo.deleteWhere(predicate)) instead of relying on the concrete NotificationsStorage methods.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@lib/shared/notifiers/session_notifier.dart`:
- Around line 129-134: The loop in _cleanup() force-unwraps session.orderId
(used in _storage.deleteSession and _sessions.remove and passed to
_cleanupSessionData) which can be null; guard against this by capturing
session.orderId into a local (e.g., orderId), check for null and skip that
session (or handle it explicitly) before calling
_storage.deleteSession(orderId), _sessions.remove(orderId), and
_cleanupSessionData(session), mirroring the null-safety fix you applied in
init().
- Around line 85-100: The init() loops currently force-unwrap session.orderId!
(in the _isForever branch and the else branch) which can throw for malformed
sessions; update both loops in init() to null-check session.orderId before using
it (skip entries where orderId is null), only assign to _sessions[orderId] and
call _storage.deleteSession(orderId) / _cleanupSessionData(session) when orderId
!= null, and ensure you do not assume orderId downstream when interacting with
the _sessions map or calling _storage.deleteSession and _cleanupSessionData.
---
Nitpick comments:
In `@lib/shared/notifiers/session_notifier.dart`:
- Around line 63-66: The code in session_notifier.dart reads
notificationsRepositoryProvider and force-casts it to NotificationsStorage
(notificationsStore = ref.read(... ) as NotificationsStorage), which breaks the
NotificationsRepository abstraction and can fail if implementation changes;
update the NotificationsRepository interface to include the needed deletion
operation (e.g., add deleteWhere or deleteByOrderId(String orderId)) and replace
the cast with the interface type (NotificationsRepository notificationsRepo)
then call the new repository method (e.g.,
notificationsRepo.deleteByOrderId(orderId) or
notificationsRepo.deleteWhere(predicate)) instead of relying on the concrete
NotificationsStorage methods.
In `@lib/shared/widgets/trade_history_selector.dart`:
- Around line 8-83: Summary: Make the Icon in TradeHistorySelector build
constant to improve performance. Change the non-const Icon(...) instance inside
the DropdownMenuItem builder (the Icon using Icons.schedule,
AppTheme.mostroGreen, size: 20) to a const Icon so the widget becomes
compile-time constant; ensure the surrounding Row children that include const
SizedBox and Text remain valid (you may need to adjust const placement on the
Text if localization prevents const). Update the Icon instantiation in the
_optionKeys.map builder accordingly.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 5572a42e-145f-431d-99f1-2979c85490d8
📒 Files selected for processing (2)
lib/shared/notifiers/session_notifier.dartlib/shared/widgets/trade_history_selector.dart
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
lib/shared/notifiers/session_notifier.dart (1)
142-144:⚠️ Potential issue | 🟡 MinorApply retention changes immediately.
updateSettings()only swaps_settings. If the user changes fromneveror a longer preset to a shorter one, already-expired sessions stay in_sessionsand in the emitted state until the next periodic cleanup tick, so the new setting does not take effect right away.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/shared/notifiers/session_notifier.dart` around lines 142 - 144, updateSettings currently only replaces _settings leaving already-expired sessions in _sessions until the next periodic cleanup; after assigning _settings = settings.copyWith() immediately apply the new retention by removing/filtering expired sessions from _sessions (or call the existing cleanup method if one exists), then emit the updated state so the change takes effect immediately (i.e., updateSettings should update _settings, prune _sessions according to the new settings, and emit the new state).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@lib/shared/notifiers/session_notifier.dart`:
- Around line 56-79: Make the session cleanup best-effort by catching and
logging errors so a single failure can't abort the notifier: wrap each storage
operation in _cleanupSessionData (eventStore.deleteWhere,
mostroStore.deleteAllMessagesByOrderId, notificationsRepo.deleteByOrderId,
SharedPreferences.remove calls) in try/catch blocks (or wrap the whole
per-session sequence in a try/catch) and log failures using the pre-configured
singleton logger from package:mostro_mobile/services/logger_service.dart instead
of creating a new Logger; apply the same pattern to the other cleanup call sites
(init and _cleanup) so individual errors are logged and swallowed while allowing
remaining sessions to proceed.
---
Outside diff comments:
In `@lib/shared/notifiers/session_notifier.dart`:
- Around line 142-144: updateSettings currently only replaces _settings leaving
already-expired sessions in _sessions until the next periodic cleanup; after
assigning _settings = settings.copyWith() immediately apply the new retention by
removing/filtering expired sessions from _sessions (or call the existing cleanup
method if one exists), then emit the updated state so the change takes effect
immediately (i.e., updateSettings should update _settings, prune _sessions
according to the new settings, and emit the new state).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 24011443-b30a-4584-a4e8-236537851317
📒 Files selected for processing (3)
lib/data/repositories/notifications_history_repository.dartlib/shared/notifiers/session_notifier.dartlib/shared/widgets/trade_history_selector.dart
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
♻️ Duplicate comments (2)
lib/shared/notifiers/session_notifier.dart (2)
131-140:⚠️ Potential issue | 🟡 MinorSame null-safety concern in
_cleanup().Line 133 and 134 force-unwrap
session.orderId!without a null check. Apply the same guard pattern here for consistency with theinit()fix.🛡️ Proposed fix
for (final session in expiredSessions) { + if (session.orderId == null) continue; if (session.startTime.isBefore(cutoff)) { await _storage.deleteSession(session.orderId!); _sessions.remove(session.orderId!); try { await _cleanupSessionData(session); } catch (e) { logger.e('Failed to cleanup data for session ${session.orderId}: $e'); } } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/shared/notifiers/session_notifier.dart` around lines 131 - 140, In _cleanup() avoid force-unwrapping session.orderId; mirror the guard used in init(): check if session.orderId is null before calling _storage.deleteSession, _sessions.remove and _cleanupSessionData (e.g., if (session.orderId == null) continue or log and continue), and only use the non-null orderId afterwards to prevent null-safety violations when invoking _storage.deleteSession, _sessions.remove, and _cleanupSessionData for that session.
83-102:⚠️ Potential issue | 🟡 MinorApply null-safety guard for
session.orderIdininit()loops.Lines 85, 92, 94, and 95 force-unwrap
session.orderId!without first checking for null. If a session has a nullorderId, this will throw a runtime exception before the guarded_cleanupSessionDatais reached. Add a null check at the start of each loop iteration.🛡️ Proposed defensive approach
Future<void> init() async { final allSessions = await _storage.getAllSessions(); if (_isForever) { for (final session in allSessions) { + if (session.orderId == null) continue; _sessions[session.orderId!] = session; } } else { final cutoff = DateTime.now() .subtract(Duration(hours: _expirationHours)); for (final session in allSessions) { + if (session.orderId == null) continue; if (session.startTime.isAfter(cutoff)) { _sessions[session.orderId!] = session; } else { await _storage.deleteSession(session.orderId!); _sessions.remove(session.orderId!);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/shared/notifiers/session_notifier.dart` around lines 83 - 102, The init() loop uses session.orderId! in multiple places and can throw if orderId is null; add a null-safety guard at the start of each iteration (inside both branches that iterate allSessions) by reading final id = session.orderId and skipping the iteration if id == null (continue), then use id instead of session.orderId! when assigning to _sessions, calling _storage.deleteSession(id), and in the logger/_cleanupSessionData calls; this ensures _cleanupSessionData and the delete path only run for sessions with a non-null orderId.
🧹 Nitpick comments (1)
lib/shared/notifiers/session_notifier.dart (1)
56-79: Consider per-operation error handling for resilience.While the callers wrap
_cleanupSessionDatain try/catch, a failure in any individual operation (e.g.,eventStore.deleteWhere) will prevent subsequent cleanup steps from executing. For best-effort cleanup, wrapping each storage operation individually would allow partial success.That said, the current approach does log failures and continue processing other sessions, which may be acceptable if cleanup failures are rare.
♻️ Optional: per-operation try/catch for maximum resilience
Future<void> _cleanupSessionData(Session session) async { final orderId = session.orderId; if (orderId == null) { logger.w('Skipping cleanup for session with null orderId'); return; } final eventStore = ref.read(eventStorageProvider); final mostroStore = ref.read(mostroStorageProvider); final notificationsRepo = ref.read(notificationsRepositoryProvider); - await eventStore.deleteWhere(Filter.equals('order_id', orderId)); + try { + await eventStore.deleteWhere(Filter.equals('order_id', orderId)); + } catch (e) { + logger.e('Failed to delete events for order $orderId: $e'); + } if (session.disputeId != null) { - await eventStore.deleteWhere( - Filter.equals('dispute_id', session.disputeId)); + try { + await eventStore.deleteWhere( + Filter.equals('dispute_id', session.disputeId)); + } catch (e) { + logger.e('Failed to delete dispute events for ${session.disputeId}: $e'); + } } - await mostroStore.deleteAllMessagesByOrderId(orderId); - await notificationsRepo.deleteByOrderId(orderId); + try { + await mostroStore.deleteAllMessagesByOrderId(orderId); + } catch (e) { + logger.e('Failed to delete mostro messages for order $orderId: $e'); + } + try { + await notificationsRepo.deleteByOrderId(orderId); + } catch (e) { + logger.e('Failed to delete notifications for order $orderId: $e'); + } final prefs = await SharedPreferences.getInstance(); - await prefs.remove('chat_last_read_$orderId'); + try { + await prefs.remove('chat_last_read_$orderId'); + } catch (e) { + logger.e('Failed to remove chat_last_read pref for order $orderId: $e'); + } if (session.disputeId != null) { - await prefs.remove('dispute_last_read_${session.disputeId}'); + try { + await prefs.remove('dispute_last_read_${session.disputeId}'); + } catch (e) { + logger.e('Failed to remove dispute_last_read pref: $e'); + } } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/shared/notifiers/session_notifier.dart` around lines 56 - 79, The _cleanupSessionData function performs multiple storage/removal operations atomically so a failure in one (e.g., eventStore.deleteWhere, mostroStore.deleteAllMessagesByOrderId, notificationsRepo.deleteByOrderId, prefs.remove) aborts the rest; change it to perform each operation in its own try/catch so failures are logged per-operation but do not prevent subsequent cleanup steps, i.e., wrap each call (including both eventStore.deleteWhere calls, mostroStore.deleteAllMessagesByOrderId, notificationsRepo.deleteByOrderId, and each prefs.remove) in small try/catch blocks that log the error via logger.e/w and continue.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@lib/shared/notifiers/session_notifier.dart`:
- Around line 131-140: In _cleanup() avoid force-unwrapping session.orderId;
mirror the guard used in init(): check if session.orderId is null before calling
_storage.deleteSession, _sessions.remove and _cleanupSessionData (e.g., if
(session.orderId == null) continue or log and continue), and only use the
non-null orderId afterwards to prevent null-safety violations when invoking
_storage.deleteSession, _sessions.remove, and _cleanupSessionData for that
session.
- Around line 83-102: The init() loop uses session.orderId! in multiple places
and can throw if orderId is null; add a null-safety guard at the start of each
iteration (inside both branches that iterate allSessions) by reading final id =
session.orderId and skipping the iteration if id == null (continue), then use id
instead of session.orderId! when assigning to _sessions, calling
_storage.deleteSession(id), and in the logger/_cleanupSessionData calls; this
ensures _cleanupSessionData and the delete path only run for sessions with a
non-null orderId.
---
Nitpick comments:
In `@lib/shared/notifiers/session_notifier.dart`:
- Around line 56-79: The _cleanupSessionData function performs multiple
storage/removal operations atomically so a failure in one (e.g.,
eventStore.deleteWhere, mostroStore.deleteAllMessagesByOrderId,
notificationsRepo.deleteByOrderId, prefs.remove) aborts the rest; change it to
perform each operation in its own try/catch so failures are logged per-operation
but do not prevent subsequent cleanup steps, i.e., wrap each call (including
both eventStore.deleteWhere calls, mostroStore.deleteAllMessagesByOrderId,
notificationsRepo.deleteByOrderId, and each prefs.remove) in small try/catch
blocks that log the error via logger.e/w and continue.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: afd6ae56-c897-4568-8606-ed15ee79ae48
📒 Files selected for processing (3)
lib/data/repositories/notifications_history_repository.dartlib/shared/notifiers/session_notifier.dartlib/shared/widgets/trade_history_selector.dart
There was a problem hiding this comment.
Review
16-file change implementing configurable trade history retention. Well-scoped and thoroughly tested.
Settings / SettingsNotifier
sessionExpirationHours added to Settings model with full serialization support. copyWith has clearSessionExpiration flag for explicit null reset. updateSessionExpirationHours persists and logs the change. Correct.
SessionNotifier
_expirationHours getter reads from user settings with Config fallback. _isForever cleanly handles the "never" mode (value 0). Cascade cleanup in both init() and _cleanup() — when a session expires, it now deletes associated data from eventStore, mostroStorage, notificationsRepository, and SharedPreferences. Each cleanup step has try/catch with logger.e() so failures are logged but do not propagate. _isForever guard in _cleanup() skips the timer entirely — correct, no wasted work.
TradeHistorySelector
Dropdown with 6 presets (1w, 1m, 3m, 6m, 1y, never), all labels via ARB localization. Smart fallback when stored value does not match any preset — falls back to Config default. Uses the widget pattern consistently with the rest of the settings screen.
app_init_provider
Reads sessionExpirationHours from settings at startup. "Never" mode correctly bypasses cutoff filtering (cutoff = null).
notifications_history_repository
New deleteByOrderId method enables cascade deletion from the notifications store.
Docs
TIMEOUT_DETECTION_AND_SESSION_CLEANUP.md table documenting what gets cleaned up and what does NOT (and why — dedup stubs in eventStore are intentionally retained to prevent duplicate event processing). This is important context that belongs in the docs.
Tests
11 tests covering: defaults, Config fallback, copyWith roundtrip, clear flag, "never" mode (value 0), full serialization roundtrip, null handling, backward compat with old JSON, all preset values. Good coverage.
CodeRabbit findings (3)
-
Hardcoded 720 in TradeHistorySelector — partially valid.
_optionKeyshardcodes the preset that matchesConfig.sessionExpirationHours. Could use the Config constant as the map key source, but functionally correct. -
No validation of invalid values in settings_notifier — mitigated by design.
TradeHistorySelectoronly passes values from_optionKeys; no other public call site exists. Defensive validation is optional. -
session.orderId!force-unwrap in_cleanup()— consistent with the same pattern already used ininit()and throughout the codebase. Not a bug.
CI
Flutter checks: all passing.
LGTM. Complete, well-tested, good docs.
AndreaDiazCorreia
left a comment
There was a problem hiding this comment.
@Catrya, I've been testing this branch and wanted to share a few things.
One thing that happened to me: when I started the app on this branch, it deleted my previous session right away, and I don't think that should happen.
Another thing, it seems like it's not checking whether there are active trades, and I think it would be a good idea, just as a precaution, to evaluate that before deleting anything.
And one more, about UX: the text that's currently in the info message of the widget should actually be shown as a confirmation dialog before applying the change, so the user confirms they understand what this change means.
- Add session expiration selector with 6 presets: 1 week, 1 month, 3 months, 6 months, 1 year, never - Default remains 1 month (720 hours), stored in Settings and persisted via SharedPreferences - SessionNotifier reads expiration from user settings with Config fallback - Value 0 means never delete, protected by explicit guards in init() and cleanup() - New Trade History card in settings screen between Notifications and Dev Tools - Localization keys added for EN, ES, IT, FR, DE - 11 unit tests covering serialization, presets, backward compatibility, and never mode - Includes log output when user changes the setting
- When a session expires, now also deletes: chat events, dispute chat events, mostro messages, notifications, and read status keys - Extracted shared cleanup logic into _cleanupSessionData() used by both init() and periodic cleanup - MostroService dedup stubs intentionally retained to prevent duplicate event processing - Updated architecture docs to reflect cascading cleanup and user-configurable retention
…feedback - Add null orderId guard with logging in _cleanupSessionData() - Replace hardcoded 720 with Config.sessionExpirationHours in trade history selector
…onst icon - Add deleteByOrderId() to NotificationsRepository interface instead of casting to concrete implementation - Mark Icon widget as const in trade history selector
…cking others - Wrap _cleanupSessionData calls in try/catch in both init() and _cleanup() - Errors are logged but don't abort remaining session cleanup
e892c8a to
280a0be
Compare
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
lib/shared/notifiers/session_notifier.dart (2)
143-167:⚠️ Potential issue | 🟠 MajorDon’t tie
_pendingChildSessionsto the history-retention window.These entries are temporary child-order placeholders, not retained trade history. Using
_expirationHourshere keeps dead trade keys instatefor a week/month/year, and Line 143 skips their cleanup forever in “never” mode. Give them a dedicated short TTL and prune them before the_isForeverearly return.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/shared/notifiers/session_notifier.dart` around lines 143 - 167, The cleanup currently ties temporary child-order placeholders (_pendingChildSessions) to the long-term retention window (_expirationHours) and is skipped when _isForever is true; move pruning of _pendingChildSessions to run before the early return and use a dedicated short TTL (e.g., _pendingChildTTLHours or similar) instead of _expirationHours, and remove entries whose session.startTime is older than DateTime.now().subtract(Duration(hours: _pendingChildTTLHours)); update any references to _pendingChildSessions removal logic in this file (and add the new TTL constant/field) so child placeholders are pruned independently of _isForever and the trade history retention window.
136-170:⚠️ Potential issue | 🟠 MajorChange
_cleanup()to returnFuture<void>and add top-level error handling.Line 148 declares
void _cleanup() async, which means async failures from_storage.getAllSessions(),_isActiveSession(), or_storage.deleteSession()will escape as uncaught errors instead of being logged. This allows cleanup to fail silently, causing orphan sessions to accumulate.Change the return type to
Future<void>and wrap the method body in a try-catch to log cleanup failures:Suggested fix
void _scheduleCleanup() { _cleanupTimer?.cancel(); _cleanupTimer = Timer.periodic( const Duration(minutes: Config.cleanupIntervalMinutes), - (timer) => _cleanup(), + (_) { + unawaited(_cleanup()); + }, ); } - void _cleanup() async { - if (_isForever) return; - - final cutoff = DateTime.now() - .subtract(Duration(hours: _expirationHours)); - final expiredSessions = await _storage.getAllSessions(); - - for (final session in expiredSessions) { - if (session.startTime.isBefore(cutoff)) { - if (await _isActiveSession(session)) { - logger.i('Skipping cleanup for active session ${session.orderId}'); - continue; - } - await _storage.deleteSession(session.orderId!); - _sessions.remove(session.orderId!); - try { - await _cleanupSessionData(session); - } catch (e) { - logger.e('Failed to cleanup data for session ${session.orderId}: $e'); - } - } - } - - _pendingChildSessions.removeWhere( - (_, session) => session.startTime.isBefore(cutoff), - ); - - _emitState(); + Future<void> _cleanup() async { + try { + if (_isForever) return; + + final cutoff = DateTime.now() + .subtract(Duration(hours: _expirationHours)); + final expiredSessions = await _storage.getAllSessions(); + + for (final session in expiredSessions) { + if (session.startTime.isBefore(cutoff)) { + if (await _isActiveSession(session)) { + logger.i('Skipping cleanup for active session ${session.orderId}'); + continue; + } + await _storage.deleteSession(session.orderId!); + _sessions.remove(session.orderId!); + try { + await _cleanupSessionData(session); + } catch (e) { + logger.e('Failed to cleanup data for session ${session.orderId}: $e'); + } + } + } + + _pendingChildSessions.removeWhere( + (_, session) => session.startTime.isBefore(cutoff), + ); + + _emitState(); + } catch (e) { + logger.e('Periodic session cleanup failed: $e'); + } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/shared/notifiers/session_notifier.dart` around lines 136 - 170, The _cleanup method should return Future<void> and add top-level error handling: change the signature from "void _cleanup() async" to "Future<void> _cleanup() async" and wrap the entire body in a try-catch that logs failures via logger.e (so failures from _storage.getAllSessions(), _isActiveSession(), _storage.deleteSession(), or _cleanupSessionData() are caught and logged); keep existing per-session try-catch for _cleanupSessionData() but ensure any unexpected errors in the whole cleanup flow are caught, then still call _emitState() in a finally block so state is always emitted and cleanup of _pendingChildSessions continues.
🧹 Nitpick comments (1)
lib/shared/notifiers/session_notifier.dart (1)
14-14: Use the model barrel for the newOrderdependency.This adds another direct model-file import in a codepath that should be coming through the barrel instead. Please pull
Orderfrompackage:mostro_mobile/data/models.darthere.♻️ Minimal import change
-import 'package:mostro_mobile/data/models/order.dart'; +import 'package:mostro_mobile/data/models.dart' show Order;As per coding guidelines
**/{lib,test}/**/*.dart: Import models throughdata/models.dartinstead of importing individual model files.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/shared/notifiers/session_notifier.dart` at line 14, The file session_notifier.dart currently imports the Order model directly; replace the direct import of package:mostro_mobile/data/models/order.dart with the model barrel package:mostro_mobile/data/models.dart so Order is resolved via the shared models barrel (keep other imports intact and ensure no name collisions with existing symbols in SessionNotifier).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@lib/shared/notifiers/session_notifier.dart`:
- Around line 143-167: The cleanup currently ties temporary child-order
placeholders (_pendingChildSessions) to the long-term retention window
(_expirationHours) and is skipped when _isForever is true; move pruning of
_pendingChildSessions to run before the early return and use a dedicated short
TTL (e.g., _pendingChildTTLHours or similar) instead of _expirationHours, and
remove entries whose session.startTime is older than
DateTime.now().subtract(Duration(hours: _pendingChildTTLHours)); update any
references to _pendingChildSessions removal logic in this file (and add the new
TTL constant/field) so child placeholders are pruned independently of _isForever
and the trade history retention window.
- Around line 136-170: The _cleanup method should return Future<void> and add
top-level error handling: change the signature from "void _cleanup() async" to
"Future<void> _cleanup() async" and wrap the entire body in a try-catch that
logs failures via logger.e (so failures from _storage.getAllSessions(),
_isActiveSession(), _storage.deleteSession(), or _cleanupSessionData() are
caught and logged); keep existing per-session try-catch for
_cleanupSessionData() but ensure any unexpected errors in the whole cleanup flow
are caught, then still call _emitState() in a finally block so state is always
emitted and cleanup of _pendingChildSessions continues.
---
Nitpick comments:
In `@lib/shared/notifiers/session_notifier.dart`:
- Line 14: The file session_notifier.dart currently imports the Order model
directly; replace the direct import of
package:mostro_mobile/data/models/order.dart with the model barrel
package:mostro_mobile/data/models.dart so Order is resolved via the shared
models barrel (keep other imports intact and ensure no name collisions with
existing symbols in SessionNotifier).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 588f207b-51af-4c0c-a4a1-8f1aeb659913
📒 Files selected for processing (17)
docs/architecture/APP_INITIALIZATION_ANALYSIS.mddocs/architecture/SESSION_AND_KEY_MANAGEMENT.mddocs/architecture/TIMEOUT_DETECTION_AND_SESSION_CLEANUP.mdlib/data/models/enums/status.dartlib/data/repositories/notifications_history_repository.dartlib/features/settings/settings.dartlib/features/settings/settings_notifier.dartlib/features/settings/settings_screen.dartlib/l10n/intl_de.arblib/l10n/intl_en.arblib/l10n/intl_es.arblib/l10n/intl_fr.arblib/l10n/intl_it.arblib/shared/notifiers/session_notifier.dartlib/shared/providers/app_init_provider.dartlib/shared/widgets/trade_history_selector.darttest/features/settings/session_expiration_test.dart
✅ Files skipped from review due to trivial changes (3)
- docs/architecture/APP_INITIALIZATION_ANALYSIS.md
- lib/l10n/intl_es.arb
- lib/l10n/intl_en.arb
🚧 Files skipped from review as they are similar to previous changes (7)
- lib/features/settings/settings_screen.dart
- lib/shared/providers/app_init_provider.dart
- lib/l10n/intl_it.arb
- lib/l10n/intl_de.arb
- lib/shared/widgets/trade_history_selector.dart
- lib/l10n/intl_fr.arb
- docs/architecture/TIMEOUT_DETECTION_AND_SESSION_CLEANUP.md
Summary by CodeRabbit
New Features
Behavior
Internationalization
Tests
Documentation