Skip to content

Mobile posthog analytics#3769

Merged
Baalmart merged 5 commits into
stagingfrom
mobile-posthog-analytics
Jul 8, 2026
Merged

Mobile posthog analytics#3769
Baalmart merged 5 commits into
stagingfrom
mobile-posthog-analytics

Conversation

@Mozart299

@Mozart299 Mozart299 commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features
    • Added broader in-app analytics coverage for key journeys like authentication, surveys, learning, dashboard views, and sharing.
    • Improved sharing experiences with better tracking across air-quality share options and submission flows.
  • Bug Fixes
    • Standardized screen navigation so app sections and redirects are more reliably recognized.
    • Improved session handling for login, logout, and guest use to keep user state in sync.

Mozart299 added 2 commits July 8, 2026 11:20
- Remove research_mode feature-flag gate so events capture for all users;
  drop the now-unused flag and isAnalyticsEnabled getter
- Add forecast events (forecast_viewed, forecast_scope_changed)
- Add share funnel events (share_sheet_opened with source, share_tab_selected,
  share_completed gated on ShareResult success)
- Add Clean Air Forum filter funnel events (tab opened, selfie source/capture,
  filter shared, wall consent and submission sent/failed)
- Return ShareResult from AirQualityShareService so only real shares count
- Enable lifecycle event capture and switch personProfiles to identifiedOnly
- Add PosthogObserver for automatic $screen tracking and name all
  MaterialPageRoute push sites via RouteSettings
- Register is_guest super property on guest entry/login; reset identity on
  session expiry fallback to guest
- Delete unused manual session tracking (SDK manages sessions)
- Split AnalyticsService: core transport (capture/identity/super props) stays
  in analytics_service.dart; typed event wrappers move to per-domain extension
  files under services/analytics/, re-exported so call sites are unchanged
- Inject AnalyticsService into AuthBloc and SurveyBloc (defaulting to the
  singleton) so bloc tests can mock analytics, matching the repository
  injection convention
- Fold share-tab label and analytics name into the _ShareTab enhanced enum,
  restoring the single-entry-per-tab promise
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@Mozart299, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 26 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1d28e79f-e5a1-4ae5-b151-3a8013faf523

📥 Commits

Reviewing files that changed from the base of the PR and between 94b835e and c942ed1.

📒 Files selected for processing (5)
  • src/mobile/lib/src/app/auth/bloc/auth_bloc.dart
  • src/mobile/lib/src/app/dashboard/widgets/air_quality_share_sheet.dart
  • src/mobile/lib/src/app/shared/services/analytics_service.dart
  • src/mobile/lib/src/app/surveys/bloc/survey_bloc.dart
  • src/mobile/test/widget_test.dart
📝 Walkthrough

Walkthrough

This PR expands PostHog analytics across the mobile app: it enables lifecycle capture, adds a navigator observer for screen tracking, refactors AnalyticsService into domain-specific event extensions, injects AnalyticsService into AuthBloc and SurveyBloc, updates share flows to return ShareResult with success-based tracking, adjusts feature flag handling, and attaches named RouteSettings to dozens of navigation calls app-wide.

Changes

Analytics Instrumentation and Navigation Tracking

Layer / File(s) Summary
PostHog config and core service
src/mobile/lib/main.dart, .../analytics_service.dart, .../feature_flag_service.dart
Enables lifecycle capture, sets identified-only profiles, adds PosthogObserver, adds markGuestSession(), registers is_guest on identify, removes the analytics-enabled gate from trackEvent, drops researchMode flag and isAnalyticsEnabled getter, removes ?? false fallback in reloadFlags.
Domain analytics extensions
.../services/analytics/analytics_app_events.dart, analytics_auth_events.dart, analytics_dashboard_events.dart, analytics_learn_events.dart, analytics_share_events.dart, analytics_survey_events.dart
New extensions on AnalyticsService provide typed trackX helpers for app, auth, dashboard, learn, share, and survey events, re-exported from analytics_service.dart.
Auth bloc analytics wiring
.../auth/bloc/auth_bloc.dart
AuthBloc accepts injected AnalyticsService; login, registration, logout, OAuth, and refresh-failure paths now call tracking/identity methods via the injected instance instead of constructing new services.
Survey bloc analytics wiring
.../surveys/bloc/survey_bloc.dart
SurveyBloc accepts injected AnalyticsService; presentation, start, question view/answer, completion, and failure events use the injected instance.
Share sheet analytics & share service
.../shared/services/air_quality_share_service.dart, .../dashboard/widgets/air_quality_share_sheet.dart, analytics_specifics.dart, .../dashboard/pages/forecast_overview_page.dart
Share methods now return Future<ShareResult>; share sheet requires a source param, reworks tabs into an enum with analytics, tracks selfie source/capture, CAF submission success/failure, sticker share completion, and consent; forecast/dashboard call sites pass source.
Named route settings for screen tracking
src/mobile/lib/src/app/** (auth, dashboard, profile, exposure, learn, survey pages/widgets), .../shared/services/navigation_service.dart
Numerous MaterialPageRoute/pushReplacement calls now include RouteSettings(name: ...) so the navigator observer can capture named $screen events.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Suggested labels: mobile-app, analytics

Suggested reviewers: Baalmart

Poem

A rabbit hopped through routes named clear,
Tracking taps and screens with cheer,
PostHog whispers "guest" or "known,"
Analytics extensions neatly sown,
Share sheets now know their source and fate —
Ship it, friends, the logs await! 🐇📊

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title is related to the PR, but it's too generic to convey the main change clearly. Use a more specific title that mentions the key update, such as PostHog analytics changes and route naming/observer tracking.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch mobile-posthog-analytics

Warning

Review ran into problems

🔥 Problems

Errors were encountered while retrieving linked issues.

Errors (1)
  • JIRA integration encountered authorization issues. Please disconnect and reconnect the integration in the CodeRabbit UI.

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.

PosthogObserver fires Posthog().screen on navigation; in tests the channel
throws MissingPluginException, which the SDK only guards as
PlatformException, failing the app-boot test.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
src/mobile/lib/src/app/surveys/bloc/survey_bloc.dart (1)

258-262: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

trackSurveyCompleted fires unconditionally regardless of sync success.

repository.submitSurveyResponse returns a success flag that distinguishes server-synced submissions from locally-saved-only ones. However, trackSurveyCompleted is called without passing this flag, and trackSurveySubmissionFailed only fires on exceptions. When success == false (saved locally, not synced), the event is still recorded as "completed" with no synced property — making it impossible to distinguish the two outcomes in analytics dashboards.

Consider adding a synced property to the event, or calling trackSurveySubmissionFailed when success == false.

♻️ Proposed refactor: pass sync status to analytics
       await analytics.trackSurveyCompleted(
         surveyId: currentState.survey.id,
         responseTime: completionTime.inSeconds,
         deviceId: currentState.currentResponse.deviceId,
+        synced: success,
       );

This requires adding a synced parameter to trackSurveyCompleted in analytics_survey_events.dart:

   Future<void> trackSurveyCompleted({
     String? surveyId,
     int? responseTime,
     String? deviceId,
+    bool? synced,
   }) =>
       trackEvent('survey_completed', properties: {
         if (surveyId != null) 'survey_id': surveyId,
         if (responseTime != null) 'response_time': responseTime,
         if (deviceId != null) 'device_id': deviceId,
+        if (synced != null) 'synced': synced,
       });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/mobile/lib/src/app/surveys/bloc/survey_bloc.dart` around lines 258 - 262,
`trackSurveyCompleted` in `SurveyBloc` is being emitted even when
`repository.submitSurveyResponse` reports `success == false`, so analytics can’t
distinguish synced vs locally saved submissions. Update the survey completion
flow to thread the sync result from `submitSurveyResponse` into
`trackSurveyCompleted` (for example by adding a `synced` parameter in
`analytics_survey_events.dart`), or route unsynced outcomes to
`trackSurveySubmissionFailed` when `success` is false. Make sure the analytics
call sites in `SurveyBloc` and the event API stay aligned.
src/mobile/lib/src/app/profile/pages/profile_page.dart (1)

162-169: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

RouteSettings formatting could be simplified.

The RouteSettings is split across four lines for a simple name string. Collapsing to a single line improves readability and matches the style used in other files (e.g., welcome_screen.dart line 131).

♻️ Suggested formatting cleanup
                                             onTap: () => Navigator.of(context)
                                                 .push(MaterialPageRoute(
-                                                    settings:
-                                                        const RouteSettings(
-                                                            name:
-                                                                'edit_profile'),
+                                                    settings: const RouteSettings(name: 'edit_profile'),
                                                     builder: (context) =>
                                                         EditProfile())),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/mobile/lib/src/app/profile/pages/profile_page.dart` around lines 162 -
169, Simplify the RouteSettings formatting in the Navigator push for EditProfile
by collapsing the const RouteSettings(name: 'edit_profile') into a single line.
Update the onTap callback in profile_page.dart to match the cleaner style used
elsewhere, keeping the MaterialPageRoute and EditProfile symbols unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/mobile/lib/src/app/auth/bloc/auth_bloc.dart`:
- Around line 66-74: Add the missing analytics fallback in the
`_onSessionExpired` handler so it matches the session-expiry branch in
`_onAppStarted`. In `AuthBloc`, before emitting the guest/session-expired states
from `_onSessionExpired`, call `analytics.resetUser()` and
`analytics.markGuestSession()` just like the app-start path does. Keep the
existing session-expiry flow intact, but ensure the runtime `SessionExpired`
path clears the prior identity and marks the user as guest before falling back.

In `@src/mobile/lib/src/app/dashboard/widgets/air_quality_share_sheet.dart`:
- Around line 346-349: Avoid forwarding raw exception text to analytics in the
catch block around CleanAirForumSubmissionService.submitSelfie and
AnalyticsService().trackCafWallSubmissionFailed; replace e.toString() with a
fixed error code or a sanitized failure bucket so internal API details from
thrown exceptions are not sent to PostHog. Keep the existing success/failure
flow in air_quality_share_sheet.dart, but update the failure tracking call to
use a safe, predefined value that identifies the error category without exposing
response content.

In `@src/mobile/lib/src/app/shared/services/analytics_service.dart`:
- Around line 38-46: The logout flow in _onLogoutUser resets analytics identity
but does not restore the guest super property, so make sure markGuestSession()
is called after resetUser() completes, or centralize that behavior inside
resetUser() itself. Update the logout handling in the analytics service so every
identity reset leaves Posthog in guest mode, using the existing
markGuestSession() method and the resetUser() path as the key symbols to modify.

---

Nitpick comments:
In `@src/mobile/lib/src/app/profile/pages/profile_page.dart`:
- Around line 162-169: Simplify the RouteSettings formatting in the Navigator
push for EditProfile by collapsing the const RouteSettings(name: 'edit_profile')
into a single line. Update the onTap callback in profile_page.dart to match the
cleaner style used elsewhere, keeping the MaterialPageRoute and EditProfile
symbols unchanged.

In `@src/mobile/lib/src/app/surveys/bloc/survey_bloc.dart`:
- Around line 258-262: `trackSurveyCompleted` in `SurveyBloc` is being emitted
even when `repository.submitSurveyResponse` reports `success == false`, so
analytics can’t distinguish synced vs locally saved submissions. Update the
survey completion flow to thread the sync result from `submitSurveyResponse`
into `trackSurveyCompleted` (for example by adding a `synced` parameter in
`analytics_survey_events.dart`), or route unsynced outcomes to
`trackSurveySubmissionFailed` when `success` is false. Make sure the analytics
call sites in `SurveyBloc` and the event API stay aligned.
🪄 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: 5d0dd540-100c-4595-a4b3-8805ea34abbe

📥 Commits

Reviewing files that changed from the base of the PR and between 892b4d5 and 94b835e.

📒 Files selected for processing (39)
  • src/mobile/lib/main.dart
  • src/mobile/lib/src/app/auth/bloc/auth_bloc.dart
  • src/mobile/lib/src/app/auth/pages/email_verification_screen.dart
  • src/mobile/lib/src/app/auth/pages/login_page.dart
  • src/mobile/lib/src/app/auth/pages/password_reset/forgot_password.dart
  • src/mobile/lib/src/app/auth/pages/password_reset/password_reset.dart
  • src/mobile/lib/src/app/auth/pages/password_reset/reset_link_sent.dart
  • src/mobile/lib/src/app/auth/pages/password_reset/reset_success.dart
  • src/mobile/lib/src/app/auth/pages/register_page.dart
  • src/mobile/lib/src/app/auth/pages/welcome_screen.dart
  • src/mobile/lib/src/app/auth/services/auth_validation_helper.dart
  • src/mobile/lib/src/app/dashboard/pages/dashboard_page.dart
  • src/mobile/lib/src/app/dashboard/pages/forecast_overview_page.dart
  • src/mobile/lib/src/app/dashboard/pages/location_selection/location_selection_screen.dart
  • src/mobile/lib/src/app/dashboard/widgets/air_quality_share_sheet.dart
  • src/mobile/lib/src/app/dashboard/widgets/analytics_specifics.dart
  • src/mobile/lib/src/app/dashboard/widgets/dashboard_app_bar.dart
  • src/mobile/lib/src/app/dashboard/widgets/my_places_view.dart
  • src/mobile/lib/src/app/exposure/pages/exposure_dashboard_view.dart
  • src/mobile/lib/src/app/learn/pages/learn_surveys_page.dart
  • src/mobile/lib/src/app/profile/pages/guest_about_page.dart
  • src/mobile/lib/src/app/profile/pages/guest_account_access_page.dart
  • src/mobile/lib/src/app/profile/pages/profile_page.dart
  • src/mobile/lib/src/app/profile/pages/widgets/account_deletion_handler.dart
  • src/mobile/lib/src/app/profile/pages/widgets/guest_settings_widget.dart
  • src/mobile/lib/src/app/profile/pages/widgets/settings_widget.dart
  • src/mobile/lib/src/app/shared/services/air_quality_share_service.dart
  • src/mobile/lib/src/app/shared/services/analytics/analytics_app_events.dart
  • src/mobile/lib/src/app/shared/services/analytics/analytics_auth_events.dart
  • src/mobile/lib/src/app/shared/services/analytics/analytics_dashboard_events.dart
  • src/mobile/lib/src/app/shared/services/analytics/analytics_learn_events.dart
  • src/mobile/lib/src/app/shared/services/analytics/analytics_share_events.dart
  • src/mobile/lib/src/app/shared/services/analytics/analytics_survey_events.dart
  • src/mobile/lib/src/app/shared/services/analytics_service.dart
  • src/mobile/lib/src/app/shared/services/feature_flag_service.dart
  • src/mobile/lib/src/app/shared/services/navigation_service.dart
  • src/mobile/lib/src/app/surveys/bloc/survey_bloc.dart
  • src/mobile/lib/src/app/surveys/widgets/new_survey_banner.dart
  • src/mobile/lib/src/app/surveys/widgets/survey_list_content.dart

Comment thread src/mobile/lib/src/app/auth/bloc/auth_bloc.dart
Comment thread src/mobile/lib/src/app/dashboard/widgets/air_quality_share_sheet.dart Outdated
Comment thread src/mobile/lib/src/app/shared/services/analytics_service.dart
Mozart299 added 2 commits July 8, 2026 12:02
…hygiene

- resetUser() now re-registers is_guest=true after clearing identity, making
  guest fallback an invariant of every identity reset (logout, expiry)
- Runtime SessionExpired handler resets analytics identity like the
  app-start expiry path already did
- Failure events report the exception type instead of raw e.toString() so
  API response details are not sent to PostHog (CAF wall + survey submission)
… paths

Sweep for the same class of issue as the review findings: account deletion,
the app-start error fallback, and the session-expiry cleanup-failure path all
emitted GuestUser without resetUser(), leaving events attributed to the prior
identity. Account deletion was the worst case - a deleted user's device kept
reporting under their identity. resetUser() is safe in catch blocks since it
never throws.
@Mozart299
Mozart299 requested a review from Baalmart July 8, 2026 09:06
@Baalmart
Baalmart merged commit f2321fa into staging Jul 8, 2026
22 of 24 checks passed
@Baalmart
Baalmart deleted the mobile-posthog-analytics branch July 8, 2026 11:32
@Baalmart Baalmart mentioned this pull request Jul 8, 2026
1 task
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants