Skip to content

feat: implement patient activities backend sync on navigation#213

Open
nthsneha wants to merge 4 commits into
AOSSIE-Org:mainfrom
nthsneha:feat/patient-activities-backend-sync
Open

feat: implement patient activities backend sync on navigation#213
nthsneha wants to merge 4 commits into
AOSSIE-Org:mainfrom
nthsneha:feat/patient-activities-backend-sync

Conversation

@nthsneha
Copy link
Copy Markdown

@nthsneha nthsneha commented Mar 25, 2026

📝 Description

Implements automatic activity completion tracking by syncing task state to the backend when users leave the Daily Activities screen.

🔧 Changes Made

  • Added updateActivityInBackground() method to TaskProvider for backend sync
  • Integrated background sync call when user navigates away from Activities screen
  • Added sync status tracking (loading/success/failure)
  • Fixed pubspec.yaml asset configuration

✅ Checklist

  • I have read the contributing guidelines.
  • I have added tests that prove my fix is effective or that my feature works.
  • I have added necessary documentation (if applicable).
  • Any dependent changes have been merged and published in downstream modules.

Summary by CodeRabbit

  • New Features
    • Daily activity data now syncs automatically in the background when you leave the daily activities screen.
    • Sync status is exposed so the app can indicate synchronization progress to the UI.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 25, 2026

Warning

Rate limit exceeded

@nthsneha has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 58 minutes and 38 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 58 minutes and 38 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d5242cb0-a7fc-4ad6-b647-c82989dc2e6f

📥 Commits

Reviewing files that changed from the base of the PR and between 9caf1e4 and 79b4bd5.

📒 Files selected for processing (2)
  • patient/lib/presentation/activities/daily_activities_screen.dart
  • patient/lib/provider/task_provider.dart
📝 Walkthrough

Walkthrough

Wrapped DailyActivitiesScreen's Scaffold in a PopScope and added an onPopInvokedWithResult callback; both the AppBar back button and the pop callback invoke context.read<TaskProvider>().updateActivityInBackground() before/navigation on pop. Added syncStatus state and updateActivityInBackground() to TaskProvider which sets loading state, calls updateActivityCompletion(_allTasks), and updates syncStatus based on that result (returns early if no tasks).

Changes

Cohort / File(s) Summary
Daily Activities Screen
patient/lib/presentation/activities/daily_activities_screen.dart
Wrapped screen in PopScope and added onPopInvokedWithResult to call updateActivityInBackground(). Updated AppBar back IconButton to call updateActivityInBackground() immediately before Navigator.pop(context).
Task Provider Sync Logic
patient/lib/provider/task_provider.dart
Added _syncStatus with public syncStatus getter and new Future<void> updateActivityInBackground() that returns early if _allTasks is empty, sets _syncStatus = ApiStatus.loading, calls updateActivityCompletion(_allTasks), then sets _syncStatus to the API result and notifies listeners. No new try/catch added.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Screen as DailyActivitiesScreen
    participant Provider as TaskProvider
    participant API as Backend API

    User->>Screen: Press back (AppBar or system)
    Screen->>Provider: updateActivityInBackground()
    activate Provider
    Provider->>Provider: if _allTasks.isEmpty -> return
    Provider->>Provider: _syncStatus = loading\nnotifyListeners
    Provider->>API: updateActivityCompletion(_allTasks)
    activate API
    API-->>Provider: response (success / error)
    deactivate API
    Provider->>Provider: _syncStatus = resultStatus\nnotifyListeners
    deactivate Provider
    Screen->>Screen: Navigator.pop(context)
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Suggested reviewers

  • mdmohsin7

Poem

🐰 I hop and tap, a tiny chore,

When back is pressed, I sync once more,
Tasks aligned, no loose thread seen,
Quiet hum of status green,
A rabbit's cheer for flows serene 🎀

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: implementing backend sync for patient activities when navigating away from the screen.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
patient/lib/provider/task_provider.dart (1)

43-45: Unused tasks parameter — method always uses _allTasks.

The tasks parameter is never used; line 45 always passes _allTasks to the repository. This is pre-existing code, but since updateActivityInBackground also passes _allTasks, the parameter is redundant and could cause confusion.

Consider either using the parameter or removing it to clarify the intent.

♻️ Suggested fix: Use the parameter
  Future<void> updateActivityCompletion(List<PatientTaskModel> tasks) async {
    try {
-     final result = await _patientRepository.updateActivityCompletion(tasks: _allTasks, activityId: _activityId, activitySetId: _activitySetId);
+     final result = await _patientRepository.updateActivityCompletion(tasks: tasks, activityId: _activityId, activitySetId: _activitySetId);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@patient/lib/provider/task_provider.dart` around lines 43 - 45, The method
updateActivityCompletion has an unused tasks parameter (it always passes
_allTasks into _patientRepository.updateActivityCompletion); either remove the
tasks parameter from updateActivityCompletion and all callers (e.g.,
updateActivityInBackground) or, preferably, use the passed-in tasks: change the
repository call in updateActivityCompletion to pass the tasks parameter instead
of _allTasks (ensure calls that expect to pass _allTasks remain correct),
keeping references to updateActivityCompletion and
_patientRepository.updateActivityCompletion to locate and update the code.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@patient/lib/presentation/activities/daily_activities_screen.dart`:
- Around line 64-67: Wrap the existing Scaffold in a PopScope so all back
navigation (system back, swipe-back, predictive gestures) triggers the sync;
inside the PopScope onWillPop callback call
context.read<TaskProvider>().updateActivityInBackground() and then return true
(or await the sync if you need to block) instead of relying only on the AppBar
onPressed/Navigator.pop; update references to Navigator.pop(...) in the action
button to still call Navigator.pop after triggering updateActivityInBackground
if you want immediate pop behavior.

In `@patient/lib/provider/task_provider.dart`:
- Around line 86-93: The try/catch around await
updateActivityCompletion(_allTasks) is ineffective because
updateActivityCompletion swallows errors; instead, after awaiting
updateActivityCompletion(_allTasks) check the provider's result state (e.g.
inspect _apiStatus or a returned success flag set by updateActivityCompletion)
and set _syncStatus = ApiStatus.success only when that result indicates success,
otherwise set _syncStatus = ApiStatus.failure; keep the notifyListeners() calls
around the status changes and reference updateActivityCompletion, _allTasks,
_apiStatus (or the chosen result flag), and _syncStatus when implementing this
conditional status update.

---

Nitpick comments:
In `@patient/lib/provider/task_provider.dart`:
- Around line 43-45: The method updateActivityCompletion has an unused tasks
parameter (it always passes _allTasks into
_patientRepository.updateActivityCompletion); either remove the tasks parameter
from updateActivityCompletion and all callers (e.g., updateActivityInBackground)
or, preferably, use the passed-in tasks: change the repository call in
updateActivityCompletion to pass the tasks parameter instead of _allTasks
(ensure calls that expect to pass _allTasks remain correct), keeping references
to updateActivityCompletion and _patientRepository.updateActivityCompletion to
locate and update the code.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e34721f4-c134-4cb0-81ae-e30bbbd93c2d

📥 Commits

Reviewing files that changed from the base of the PR and between 051a4f3 and ec00ad5.

📒 Files selected for processing (2)
  • patient/lib/presentation/activities/daily_activities_screen.dart
  • patient/lib/provider/task_provider.dart

Comment thread patient/lib/presentation/activities/daily_activities_screen.dart Outdated
Comment thread patient/lib/provider/task_provider.dart Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
patient/lib/presentation/activities/daily_activities_screen.dart (1)

58-75: ⚠️ Potential issue | 🟠 Major

Double sync triggered when using AppBar back button.

When the user taps the AppBar back button, updateActivityInBackground() is called twice:

  1. First in IconButton.onPressed (line 72)
  2. Then again in onPopInvokedWithResult when didPop is true (line 62)

Since updateActivityInBackground() has no concurrency guards (see task_provider.dart:82-92), this results in two simultaneous network requests to the backend.

Choose one trigger point. Given that PopScope with canPop: false properly intercepts all navigation methods (system back, swipe gestures, AppBar button), that approach is cleaner:

🛠️ Proposed fix using canPop: false pattern
     return PopScope(
-      canPop: true,
+      canPop: false,
       onPopInvokedWithResult: (didPop, result) {
-        if (didPop) {
-          context.read<TaskProvider>().updateActivityInBackground();
-        }
+        if (didPop) return;
+        context.read<TaskProvider>().updateActivityInBackground();
+        Navigator.pop(context);
       },
       child: Scaffold(
         appBar: AppBar(
           title: const Text('Daily Activities'),
           centerTitle: true,
           leading: IconButton(
             icon: const Icon(Icons.arrow_back_ios_new_rounded),
-            onPressed: () {
-              context.read<TaskProvider>().updateActivityInBackground();
-              Navigator.pop(context);
-            },
+            onPressed: () => Navigator.maybePop(context),
           ),
         ),

With this pattern:

  • canPop: false intercepts all pop attempts
  • onPopInvokedWithResult handles sync for all navigation methods (system back, swipe, AppBar)
  • IconButton delegates to Navigator.maybePop() which triggers the PopScope callback
  • Single sync call regardless of how the user navigates back
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@patient/lib/presentation/activities/daily_activities_screen.dart` around
lines 58 - 75, The AppBar back button currently triggers
updateActivityInBackground() twice because it calls it directly in
IconButton.onPressed while PopScope.onPopInvokedWithResult also calls it when
didPop is true; remove the direct call from IconButton.onPressed and change the
button to call Navigator.maybePop() so PopScope (with canPop: false) handles all
navigation and a single call to updateActivityInBackground() happens in
onPopInvokedWithResult; update references to PopScope, canPop,
onPopInvokedWithResult, IconButton.onPressed, Navigator.maybePop(), and
updateActivityInBackground() accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@patient/lib/presentation/activities/daily_activities_screen.dart`:
- Around line 58-75: The AppBar back button currently triggers
updateActivityInBackground() twice because it calls it directly in
IconButton.onPressed while PopScope.onPopInvokedWithResult also calls it when
didPop is true; remove the direct call from IconButton.onPressed and change the
button to call Navigator.maybePop() so PopScope (with canPop: false) handles all
navigation and a single call to updateActivityInBackground() happens in
onPopInvokedWithResult; update references to PopScope, canPop,
onPopInvokedWithResult, IconButton.onPressed, Navigator.maybePop(), and
updateActivityInBackground() accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e83d56d3-5c94-4ea7-ab2f-97c728a18544

📥 Commits

Reviewing files that changed from the base of the PR and between ec00ad5 and 9caf1e4.

📒 Files selected for processing (2)
  • patient/lib/presentation/activities/daily_activities_screen.dart
  • patient/lib/provider/task_provider.dart
🚧 Files skipped from review as they are similar to previous changes (1)
  • patient/lib/provider/task_provider.dart

@Varadraj75
Copy link
Copy Markdown
Contributor

Hey @nthsneha, went through the diff. The overall approach is reasonable but there are a few things to fix before this merges.

What's working

  • PopScope wrapping is the correct way to intercept system back and swipe gestures, not just the AppBar button
  • Early return in updateActivityInBackground() when _allTasks is empty is a good guard
  • After the second commit, _syncStatus = _apiStatus is the right way to mirror the result instead of always setting success

Blockers

  1. Double sync on back button tap - onPopInvokedWithResult fires with didPop: true when Navigator.pop(context) is called from onPressed. So the AppBar back button triggers updateActivityInBackground() directly in onPressed, and then immediately triggers it again inside onPopInvokedWithResult. Two concurrent network writes go out on every back tap. Fix this by removing the sync call from onPressed and letting PopScope be the single trigger:
return PopScope(
  canPop: false,
  onPopInvokedWithResult: (didPop, result) {
    if (didPop) return;
    context.read<TaskProvider>().updateActivityInBackground();
    Navigator.pop(context);
  },
  child: Scaffold(
    appBar: AppBar(
      leading: IconButton(
        icon: const Icon(Icons.arrow_back_ios_new_rounded),
        onPressed: () => Navigator.maybePop(context),
      ),
    ),
  1. No concurrency guard - if the user taps back quickly or the callback fires twice, overlapping calls race on _syncStatus and _apiStatus. Add an in-flight check at the top:
Future<void> updateActivityInBackground() async {
  if (_allTasks.isEmpty || _syncStatus == ApiStatus.loading) return;
  ...
}
  1. updateActivityCompletion has an unused tasks parameter - the method signature accepts a tasks argument but the internal repository call always passes _allTasks instead. updateActivityInBackground passes _allTasks as the argument anyway, so this is doubly redundant. Was there a reason the parameter was kept separate from _allTasks? If callers are always expected to pass _allTasks, the parameter should just be removed. If it was meant to support arbitrary task lists in the future, the repository call should use tasks instead of _allTasks.

Minor

  • syncStatus is exposed as a public getter but nothing in the UI reads it, so the loading and failure states from background sync are invisible to the user. Is there a plan to surface this? Even a brief error snackbar on failure would be useful so the user knows their progress was not saved
  • The PR description says "Fixed pubspec.yaml asset configuration" but no pubspec.yaml changes are in the diff. Was this dropped in a rebase or is the description out of date?

Fix the double sync and add the concurrency guard and this is good to merge.

@nthsneha
Copy link
Copy Markdown
Author

@Varadraj75
Made the changes

@Varadraj75
Copy link
Copy Markdown
Contributor

@nthsneha, the fixes from the last round look good. One thing still needs addressing before this merges.


Blocker

There are two context.read<TaskProvider>() calls inside onPopInvokedWithResult, and the second one happens after an await:

await context.read<TaskProvider>().updateActivityInBackground(); // fine
final taskProvider = context.read<TaskProvider>(); // unsafe, this is after the await

Calling context.read across an async gap will trip use_build_context_synchronously and can crash if the widget unmounts while the network call is in flight. Capture the provider before the await and reuse it:

onPopInvokedWithResult: (didPop, result) async {
  if (didPop) return;
  final taskProvider = context.read<TaskProvider>();
  await taskProvider.updateActivityInBackground();
  if (taskProvider.syncStatus == ApiStatus.failure) {
    if (mounted) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          content: Text('Failed to save your progress'),
          backgroundColor: Colors.red,
          duration: Duration(seconds: 2),
        ),
      );
    }
  }
  if (mounted) Navigator.pop(context);
},

Everything else looks good

  • Double sync is fixed, onPressed just calls Navigator.maybePop now and lets PopScope handle the rest
  • Concurrency guard in updateActivityInBackground is correct
  • tasks parameter removed from updateActivityCompletion, cleaner now
  • Failure snackbar is being surfaced to the user

One line change and this is ready.

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