Feature/remove legacy plan metadata#498
Conversation
- Added new dependencies: jni and jni_flutter to pubspec.lock. - Updated path_provider_android version to 2.3.1 in pubspec.lock. - Modified Flutter SDK version constraint in pubspec.lock. - Refactored connect screen to enhance group discovery and loading states. - Introduced DiscoverGroupsScreen and DiscoverGroupsSection for better UI organization. - Updated localization strings for improved user experience.
… sync process - Deleted PlanMetadataStore and SpecialPlanStartedAtStore to streamline storage management. - Updated notification sync process to directly hydrate server routines into local storage without relying on legacy metadata stores. - Adjusted notification scheduling to focus on local recitation and timer reminders, with plan reminders now delivered via server push (FCM). - Cleaned up related imports and references throughout the codebase for improved clarity and maintainability.
Confidence Score: 4/5Safe to merge with one issue to address: the FCM device re-registration triggered by a notification toggle reads stale SharedPreferences before the toggle write has landed, so the backend receives the wrong plan/series push gate on the first registration after each toggle. When the user flips the master or routine toggle, Riverpod fires the notificationProvider listener synchronously before prefs.setBool is awaited in the notifier. refreshRegistration() immediately reads the old SharedPreferences value and sends it to the backend, so the backend's push gate for plan/series notifications reflects the pre-toggle state until the next login. lib/features/push_notifications/presentation/providers/push_notification_providers.dart and lib/features/push_notifications/application/push_notification_service.dart — the interaction between the Riverpod listener firing on state change and the async SharedPreferences write in the notifier. Reviews (2): Last reviewed commit: "Update lib/features/connect/presentation..." | Re-trigger Greptile |
| ref.listen<NotificationState>( | ||
| notificationProvider, | ||
| (prev, next) { | ||
| final changed = prev == null || | ||
| prev.appMasterEnabled != next.appMasterEnabled || | ||
| prev.appRoutineEnabled != next.appRoutineEnabled; | ||
| if (changed) service.refreshRegistration(); | ||
| }, | ||
| ); |
There was a problem hiding this comment.
FCM re-registration fires on first notification-state emission
When prev == null, refreshRegistration() is called unconditionally on the first state change from notificationProvider. For a StateNotifierProvider, Riverpod sets the initial state synchronously in the constructor, so the first listener call has a non-null prev in practice — but the prev == null guard still triggers on edge cases such as provider disposal/re-creation. The result is an extra PATCH /devices call on every cold start. This is idempotent on the backend, so it's never incorrect, but it causes a redundant network round-trip immediately after login when syncAuth() already triggers _registerToken().
…ion.dart Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
| ref.listen<NotificationState>( | ||
| notificationProvider, | ||
| (prev, next) { | ||
| final changed = prev == null || | ||
| prev.appMasterEnabled != next.appMasterEnabled || | ||
| prev.appRoutineEnabled != next.appRoutineEnabled; | ||
| if (changed) service.refreshRegistration(); | ||
| }, | ||
| ); |
There was a problem hiding this comment.
Stale SharedPreferences read when preference is re-registered
The listener fires synchronously as soon as state = state.copyWith(appMasterEnabled: enable) (or the routine equivalent) is executed inside toggleMaster/toggleRoutine. At that exact moment, prefs.setBool(...) hasn't been called yet — it's still pending in the async body. refreshRegistration() immediately calls unawaited(_registerToken()), which reads StorageKeys.notificationMasterEnabled/notificationRoutineEnabled from SharedPreferences via _readPreferences(). Because the write hasn't happened, it reads the old value and sends the stale {'routine': true} to the backend even when the user just toggled master or routine OFF. The backend therefore won't suppress plan/series push until the next token registration (login).
The simplest fix is to write the preference to SharedPreferences before updating the Riverpod state, so the listener always sees the already-persisted value. Alternatively, the listener already has the correct next.appMasterEnabled / next.appRoutineEnabled values in scope and could pass them directly into a variant of refreshRegistration instead of re-reading from storage.
Uh oh!
There was an error while loading. Please reload this page.