Add remote notifications#382
Conversation
* Add necessary permission to iOS * Add firebase configurations * Add FCM service * Add senoti service to register device
…antus-apps into beast/remote-notification
…antus-apps into beast/remote-notification
|
Here's my review of PR #382: Add remote notifications. OverallSolid groundwork for FCM integration. The iOS/Android setup, permission handling, token lifecycle, and foreground/background message handling all look correct. A few issues to address though, primarily around DRY and some leftover debug code. DRY Violations1. Notification tap navigation logic -- duplicated 3 timesThe pattern of "deserialize tx event -> set intent provider -> navigate to
final json = jsonDecode(payload);
final txService = _ref.read(transactionServiceProvider);
final event = txService.deserializeTxEventFromJsonIfPossible(json);
if (event != null) {
_ref.read(transactionIntentProvider.notifier).state = event;
navigatorKey.currentState?.pushNamed('/transactions');
}
final json = jsonDecode(payload);
final txService = _ref.read(transactionServiceProvider);
final event = txService.deserializeTxEventFromJsonIfPossible(json);
if (event != null) {
_ref.read(transactionIntentProvider.notifier).state = event;
navigatorKey.currentState?.pushNamed('/transactions');
}And now the new void _handleNotificationTap(RemoteMessage message, GlobalKey<NavigatorState> navigatorKey) {
final data = message.data;
if (data.isEmpty) return;
final txService = _ref.read(transactionServiceProvider);
final event = txService.deserializeTxEventFromJsonIfPossible(data);
if (event != null) {
_ref.read(transactionIntentProvider.notifier).state = event;
navigatorKey.currentState?.pushNamed('/transactions');
}
}Suggestion: Extract a shared helper method (e.g., on 2.
|
| Category | Items |
|---|---|
| DRY violations | Notification tap handler duplicated 3x; getMainAccount() duplicated |
| Debug leftovers | Raw print() in senoti_service.dart |
| Config issue | Senoti endpoint is localhost |
| Security | Firebase keys in source control (verify intentional) |
| Stale comments | "placeholder for future" is no longer future |
| Consistency | Remote vs push notification flow split across different layers |
The core FCM integration is well-structured. Addressing the DRY items and cleaning up the debug artifacts would make this ready to merge.
|
Here's the updated review of PR #382 after the changes. What was addressed from the first reviewNice work -- several key items were fixed:
case NotificationSource.push:
case NotificationSource.remote:
_localNotificationsService.showOrScheduleNotification(notification);
break;
New bug introduced
|
| Status | Item |
|---|---|
| Fixed | DRY: Tap navigation extracted to shared method |
| Fixed | push/remote cases unified |
| Fixed | Stale addRemoteNotification stub removed |
| Fixed | Firebase files gitignored |
| Fixed | Dead getMainAccount() removed |
| New bug | jsonDecode on Map will crash FCM tap handling |
| New minor | Unused Ref ref parameter in navigateToTransactionFromPayload |
| Remaining | print() with sensitive data in senoti_service.dart |
| Remaining | Senoti endpoint hardcoded to localhost |
The jsonDecode type mismatch is a blocker -- tapping an FCM notification will crash. The rest are cleanup items.
|
print is fine but the crash might be an issue |
n13
left a comment
There was a problem hiding this comment.
Great, I now have the full picture with all 12 commits. Let me compile the updated review.
PR #382 Review -- Third Pass (Latest)
The PR has gone through significant iteration since my last two reviews. Let me go through what's been addressed and what remains.
Previously Raised Issues -- Now Fixed
-
DRY: Notification tap navigation --
navigateToTransactionFromPayloadIfPossibleis now extracted toTransactionServiceand called from all three locations. Clean. -
jsonDecodecrash on FCM tap -- Fixed. The method now takesMap<String, dynamic>?directly. Local notification callers decode the String first, FCM callers passmessage.datadirectly. No more type mismatch. -
Unused
Ref refparameter -- Removed. The method uses the class's own_ref. -
push/remotecases unified -- Clean fall-through:
case NotificationSource.push:
case NotificationSource.remote:
_localNotificationsService.showOrScheduleNotification(notification);
break;-
Dead
addRemoteNotificationstub removed -- Gone, along with the stale "placeholder for future" comment. -
Dead
getMainAccount()removed fromSenotiService. -
Firebase files added to
.gitignore-- Both**/lib/firebase_options.dartand**/ios/Runner/GoogleService-Info.plistare excluded. -
Feature flag -- Everything is gated behind
FeatureFlags.enableRemoteNotifications(currentlyfalse) inmain.dart,app_initializer.dart, andapp.dart. This is a great addition -- it means the feature is safely dormant until ready.
Remaining Items
1. Senoti endpoint is still localhost
static const String senotiEndpoint = 'http://localhost:3100/api';Since the feature flag is false, this won't be hit at runtime right now. But when the flag is flipped, this will fail on real devices. Needs a production URL before enabling.
Severity: Low (blocked by feature flag), but a blocker for turning the flag on.
2. print() statements in senoti_service.dart
Five raw print() calls remain, some logging sensitive data:
print('request challenge');
print('challenge: $ch');
print('msg: $msg'); // logs the full signature message
print('verify ${ch['temp_session_id']!} $senotiEndpointUrl');
print('verify response: ${r.body}');These will show in release builds (unlike debugPrint). The signature message and session IDs are sensitive. Again behind the feature flag, but should be cleaned up before enabling.
Severity: Low (behind flag), but a security concern when enabled.
3. No error handling on registerDevice in token refresh listener
In _setupTokenRefreshListener():
_messaging.onTokenRefresh.listen((newToken) {
_senotiService.registerDevice(newToken, Platform.operatingSystem);
});If senoti is unreachable, this throws an unhandled async error from a stream listener, which could crash the app. The init() call is wrapped in AppInitializer's try-catch so _getToken() failures are caught, but the refresh listener has no such protection.
Suggestion: Wrap in try-catch:
_messaging.onTokenRefresh.listen((newToken) async {
try {
await _senotiService.registerDevice(newToken, Platform.operatingSystem);
} catch (e) {
debugPrint('Failed to re-register device: $e');
}
});Severity: Medium -- this can crash in production when the flag is on.
4. Missing Android google-services.json
The firebase.json references android/app/google-services.json but this file isn't in the PR. Android builds will fail when the flag is turned on and Firebase tries to initialize. iOS has GoogleService-Info.plist but Android is missing its equivalent.
Severity: Low (feature is off + not tested on Android per the PR description), but needs to be added before Android testing.
5. Info.plist whitespace churn
Still a full rewrite of every line (tabs to spaces). The actual content additions are just UIBackgroundModes and FirebaseAppDelegateProxyEnabled. Cosmetic but makes the diff noisy.
Severity: Cosmetic only.
Summary
| Status | Item |
|---|---|
| Fixed | DRY: tap navigation extracted to TransactionService |
| Fixed | jsonDecode crash on FCM Map payload |
| Fixed | Unused Ref ref parameter removed |
| Fixed | push/remote cases unified |
| Fixed | Dead addRemoteNotification stub removed |
| Fixed | Dead getMainAccount() removed |
| Fixed | Firebase files gitignored |
| New (good) | Feature flag gates all FCM code (currently off) |
| Remaining | Senoti endpoint is localhost (blocker for flag-on) |
| Remaining | print() with sensitive data (security concern for flag-on) |
| Remaining | Unhandled error in token refresh listener (potential crash) |
| Remaining | Missing Android google-services.json |
| Remaining | Info.plist whitespace churn (cosmetic) |
Verdict
The feature flag makes this safe to merge as-is -- nothing will execute at runtime with enableRemoteNotifications = false. All the blocking bugs from previous reviews are fixed. The remaining items (#1-#4) must be addressed before the flag gets flipped to true, but they don't block this PR from landing. I'd approve this with a note that items 1-3 need resolution before enabling the feature.
n13
left a comment
There was a problem hiding this comment.
PR #382 Review: Add Remote Notifications
Branch: beast/remote-notification -> n13/v3_design
Commits: 14
Reviewed: 2026-02-16 (fourth pass -- latest)
What This PR Does
Adds Firebase Cloud Messaging (FCM) for remote push notifications. Key additions:
- Firebase configuration for iOS and Android
FirebaseMessagingServicefor FCM lifecycle (permissions, token, foreground/background messages, tap handling)SenotiService(inquantus_sdk) for device registration with challenge-response auth- Feature flag
enableRemoteNotifications(currentlyfalse) gating all FCM code
Issues Fixed Since Previous Reviews
All blocking bugs and DRY violations from prior reviews have been addressed:
| Item | Status |
|---|---|
| Notification tap navigation duplicated 3x | Fixed -- extracted to TransactionService.navigateToTransactionFromPayloadIfPossible |
jsonDecode crash on FCM Map payload |
Fixed -- method now takes Map<String, dynamic>? directly |
Unused Ref ref parameter |
Fixed -- removed |
push/remote notification cases split across layers |
Fixed -- unified via fall-through in addNotification |
Dead addRemoteNotification stub with stale comments |
Fixed -- removed |
Dead getMainAccount() in SenotiService |
Fixed -- removed |
| Firebase config files not gitignored | Fixed -- both added to .gitignore |
| No feature flag for safe rollout | Fixed -- FeatureFlags.enableRemoteNotifications gates all FCM code |
print() statements leaking sensitive data in senoti_service.dart |
Mostly fixed -- removed from registerDevice, 1 remains in requestChallenge |
| Unhandled error in token refresh / device registration | Fixed -- _registerDevice wrapper with try-catch |
| No early return when permission denied | Fixed -- init() checks AuthorizationStatus and returns early |
Remaining Items
1. Senoti endpoint hardcoded to localhost
static const String senotiEndpoint = 'http://localhost:3100/api';Every other endpoint in AppConstants points to a production URL. This will fail on real devices. Needs a production URL before the feature flag is turned on.
Severity: Blocker for enabling the feature. Safe while flag is off.
2. One leftover print() in SenotiAuthClient.requestChallenge
Future<Map<String, String>> requestChallenge() async {
print('request challenge'); // <-- still hereMinor, but print() outputs in release builds unlike debugPrint.
Severity: Low.
3. Missing Android google-services.json
The firebase.json references android/app/google-services.json but this file is not included in the PR. Android builds will fail when Firebase initializes. Per the PR description, Android hasn't been tested yet.
Severity: Blocker for Android. Not relevant while flag is off or iOS-only testing.
4. Info.plist whitespace churn
The entire Info.plist shows as removed and re-added due to indentation change (tabs to spaces). The actual content additions are just UIBackgroundModes (remote-notification, fetch) and FirebaseAppDelegateProxyEnabled = NO. Functional but noisy diff.
Severity: Cosmetic.
Code Quality Notes
Good patterns observed:
- Clean separation:
SenotiAuthClient(HTTP) vsSenotiService(business logic) - Proper
@pragma('vm:entry-point')on the background handler FirebaseAppDelegateProxyEnabled = NOwith manual APNs token forwarding inAppDelegate.swift-- avoids swizzling conflicts withflutter_local_notifications- Feature flag pattern is well-applied across all three integration points (
main.dart,app_initializer.dart,app.dart) _remoteMessageToNotificationDatacleanly maps FCM payloads to the existingNotificationDatamodel
Verdict
Approve -- safe to merge with the feature flag off. All previous blocking bugs are resolved. The remaining items (localhost endpoint, missing Android config) must be addressed before flipping enableRemoteNotifications to true, but they don't block landing this PR.
|
GTG! |
* feat: make balance view no layout shifting on hide * chore: adjust margin bottom * feat: hide amount in tx history * feat: properly display tx divider * feat: adjust spacing between TX history * fix: make tx item separator less prominent * fix: make underline less prominent & spacing between underline and text * fix: background seamless gradient * fix: setting screen text styling * fix: buggy isLastItem logic * fix: extract repeating pattern into a method * Turn off remote notifications feature * feat: refactor access to isBalanceHidden to use provider for reusability * fix: not loading persisted value * feat: make boolean named param * feat: pass value to method widget instead of double watch provider * feat: improve readability, and remove redundant getter
Summary
I have finished adding FCM and permission needed in iOS. I have tested that it works in iOS but I haven't tested for android. To test it, we need real device. Later we will just need to handle the kind of message and behavior we want, all the ground works are done.
Changes