Skip to content

feat: add propertiesExtractor to PosthogObserver#352

Closed
didyk wants to merge 1 commit intoPostHog:mainfrom
didyk:feat/observer-properties-extractor
Closed

feat: add propertiesExtractor to PosthogObserver#352
didyk wants to merge 1 commit intoPostHog:mainfrom
didyk:feat/observer-properties-extractor

Conversation

@didyk
Copy link
Copy Markdown

@didyk didyk commented Apr 5, 2026

💡 Motivation and Context

Enable users to enrich $screen events with custom properties (e.g. app lifecycle state) without replacing PosthogObserver

💚 How did you test it?

Unit tests added for propertiesExtractor across didPush, didPop, didReplace

📝 Checklist

  • I reviewed the submitted code.
  • I added tests to verify the changes.
  • [ ] I updated the docs if needed.
  • No breaking change or entry added to the changelog.

If releasing new changes

  • Ran pnpm changeset to generate a changeset file
  • Added the release label to the PR

@didyk didyk requested a review from a team as a code owner April 5, 2026 20:37
@dustinbyrne
Copy link
Copy Markdown
Contributor

Hi @didyk, thank you for the contribution!

Could you share a concrete use case for this? Specifically, I'd like to understand when you'd use propertiesExtractor over calling Posthog().screen() directly with custom properties, or using register() for global properties.

What kind of per-route data would you extract?

@didyk
Copy link
Copy Markdown
Author

didyk commented Apr 7, 2026

Hi @didyk, thank you for the contribution!

Could you share a concrete use case for this? Specifically, I'd like to understand when you'd use propertiesExtractor over calling Posthog().screen() directly with custom properties, or using register() for global properties.

What kind of per-route data would you extract?

Hi @dustinbyrne, thanks for reviewing!

The main use case is filtering out "ghost" screen events caused by background widget rebuilds.

In Flutter, when the app is in the background (e.g. AppLifecycleState.paused or inactive), the OS or certain plugins can trigger a rebuild of the widget tree. When this happens, the router reconstructs the current route, and PosthogObserver automatically fires a new $screen event.

This leads to fake screen view counts in PostHog, because the user isn't actually looking at the app - it's just a background refresh.

By adding propertiesExtractor, we can tag every automatic screen event with the current app lifecycle state:

MaterialApp(
  navigatorObservers: [
    PosthogObserver(
      propertiesExtractor: (route) => {
        'app_state': WidgetsBinding.instance.lifecycleState?.name ?? 'unknown',
      },
    ),
  ],
)

Then, in PostHog queries, we can easily filter out events where app_state != 'resumed'.

Why not call screen() directly? To do that, we would have to stop using PosthogObserver entirely and manually wire up our own RouteObserver just to add this one property. PosthogObserver is incredibly convenient, and this change allows us to keep using it while enriching the data.

Why not use register()? If we use Posthog().register('app_state', state), the property becomes global. While we could update the registered property every time the AppLifecycleState changes, it risks race conditions where a background rebuild fires a $screen event before the lifecycle listener has updated the registered property. Extracting the state synchronously at the exact moment the route is pushed/popped guarantees accuracy.

Let me know if you need any changes to the PR!

@marandaneto
Copy link
Copy Markdown
Member

@didyk

The main use case is filtering out "ghost" screen events caused by background widget rebuilds.

In Flutter, when the app is in the background (e.g. AppLifecycleState.paused or inactive), the OS or certain plugins can trigger a rebuild of the widget tree. When this happens, the router reconstructs the current route, and PosthogObserver automatically fires a new $screen event.

This leads to fake screen view counts in PostHog, because the user isn't actually looking at the app - it's just a background refresh.

How can we reproduce this behavior? can you provide an MRE? If that's the case, we should implement this built-in in PosthogObserver instead, so users don't have to do this by hand with propertiesExtractor
While I see propertiesExtractor being useful as it has access to the route which super properties (register) do not, or just more boilerplate with the screen method, it looks like we are adding a new property to fix an issue that should be addressed internally, without any client's code.

@didyk
Copy link
Copy Markdown
Author

didyk commented Apr 7, 2026

@marandaneto I created a small repo - https://github.com/didyk/posthog-mre, you will need to paste your API key for check logs from posthog.

My results:
image
image

@marandaneto
Copy link
Copy Markdown
Member

@didyk, what steps should I take to reproduce the issue? Put the app in the background and wait?

@didyk
Copy link
Copy Markdown
Author

didyk commented Apr 7, 2026

@marandaneto yes,
STR:

  1. build the app and wait until you will see home screen
  2. put app to bg
  3. check logs

@marandaneto
Copy link
Copy Markdown
Member

closed by #355

@marandaneto marandaneto closed this Apr 8, 2026
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.

3 participants