Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Heatmap support #68

Open
marandaneto opened this issue Jan 2, 2024 · 6 comments
Open

Heatmap support #68

marandaneto opened this issue Jan 2, 2024 · 6 comments
Labels
enhancement New feature or request Heatmap

Comments

@marandaneto
Copy link
Member

marandaneto commented Jan 2, 2024

Description

https://posthog.com/docs/toolbar/heatmaps

We'd need to create a Widget wrapper that tracks and captures every interaction such as clicks, etc.

You can manually do this though, by capturing events manually.

@marandaneto marandaneto added enhancement New feature or request Heatmap labels Jan 2, 2024
@th8m0z
Copy link

th8m0z commented Jan 7, 2024

I love Posthog and this feature would be such a gamechanger. Can you share more details on your ideas for the implementation? I'd love to contribute.

@marandaneto
Copy link
Member Author

marandaneto commented Jan 8, 2024

To capture events manually, you have to capture an event called $autocapture with the following properties:

                      ElevatedButton(
                        onPressed: () {
                          Posthog()
                              .capture(eventName: '\$autocapture', properties: {
                            '\$event_type': 'touch',
                            '\$touch_x': 100, // read the position 
                            '\$touch_y': 100, // read the position 
                          });
                        },
                        child: const Text("Capture touch event"),
                      ),

The goal would be to do that automatically instead of calling capture manually in every single widget.

The problem is that heatmaps is only available via the toolbar (Web only), so you'd not be able to see the heatmaps for Mobile yet anyway, this would be a feature request, Heatmaps for Mobile in general.

The suggested approach would be useful anyway since more events would be captured automatically instead of manually.

We can achieve that by doing something similar to https://github.com/ueman/sentry-dart-tools/blob/main/sentry_flutter_plus/lib/src/widgets/click_tracker.dart which is a widget wrapper that then can "intercept" every click.

@IchordeDionysos
Copy link

The proposed approach seems to be rather complicated given a mobile toolbar would be required to have.
Of course having that would be amazing and the long term goal.

Though maybe I am missing something, but wouldn't it be possible if for web where clicks are automatically tracked, but the scroll position is incorrect.

We would override the scroll manager and provide it with the correct scroll position from Flutter?
https://github.com/PostHog/posthog-js/blob/339092d8f19a4004ca8c5b059cf903c87332efb5/src/scroll-manager.ts

@IchordeDionysos
Copy link

IchordeDionysos commented Jan 26, 2025

Here is a quick proof of concept for this ☺
https://github.com/user-attachments/assets/3cbab4c6-5ef3-4be8-b569-95a85e69fcec

It's not ideal, e.g. if some areas like a nav bar are not scrollable.

child: NotificationListener(
  onNotification: (notification) {
    if (notification is ScrollMetricsNotification) {
      if (notification.metrics.axis == Axis.vertical) {
        updateScrollData(createJSInteropWrapper(ScrollContext(
          // todo: Verify which numbers to put here exactly.
          maxScrollHeight: notification.metrics.maxScrollExtent,
          maxScrollY: notification.metrics.maxScrollExtent,
          lastScrollY: notification.metrics.pixels,
          maxContentHeight: notification.metrics.maxScrollExtent +
              notification.metrics.viewportDimension,
          maxContentY: notification.metrics.maxScrollExtent +
              notification.metrics.viewportDimension,
          lastContentY: notification.metrics.pixels +
              notification.metrics.viewportDimension,
        )));
      }
    }
    return false;
  },
@JSExport()
class ScrollContext {
  ScrollContext({
    this.maxScrollHeight,
    this.maxScrollY,
    this.lastScrollY,
    this.maxContentHeight,
    this.maxContentY,
    this.lastContentY,
  });

  double? maxScrollHeight;
  double? maxScrollY;
  double? lastScrollY;
  double? maxContentHeight;
  double? maxContentY;
  double? lastContentY;
}
import 'dart:js_interop';

import 'package:simpleclub_flutter/core.dart';

@JS()
external void updateScrollData(JSObject newContext);
 <script>
  // export interface ScrollContext {
  //   // scroll is how far down the page the user has scrolled,
  //   // content is how far down the page the user can view content
  //   // (e.g. if the page is 1000 tall, but the user's screen is only 500 tall,
  //   // and they don't scroll at all, then scroll is 0 and content is 500)
  //   maxScrollHeight?: number
  //   maxScrollY?: number
  //   lastScrollY?: number
  //   maxContentHeight?: number
  //   maxContentY?: number
  //   lastContentY?: number
  // }

  // This class is responsible for tracking scroll events and maintaining the scroll context
  class ScrollManagerFlutter {
    constructor(instance) {
      this.instance = instance;
    }

    getContext() {
      return this.context
    }

    resetContext() {
      const ctx = this.context

      // update the scroll properties for the new page, but wait until the next tick
      // of the event loop
      // setTimeout(this.updateScrollData, 0)

      return ctx
    }

    updateScrollData = (newContext) => {
      this.context = newContext;
    }

    startMeasuringScrollPosition() {}

    scrollElement() {
      return window.document.querySelector('flutter-view');
    }

    scrollY() {
      return this.context.lastScrollY;
    }

    scrollX() {
      return this.context.lastScrollX;
    }
  }
</script>
posthog.init('phc_VXXWtJlclxOXCSN3qGxCye5jGNEJIlMiy0ZK39pi4Hy', {
  api_host:'https://l.simpleclub.com',
  person_profiles: 'identified_only' // or 'always' to create profiles for anonymous users as well
});
setTimeout(() => {
  const scrollManagerFlutter = new ScrollManagerFlutter(posthog);
  posthog.scrollManager = scrollManagerFlutter;
  console.log('scrollManagerFlutter', scrollManagerFlutter);
}, 1000);
function updateScrollData(newContext) {
  posthog.scrollManager.updateScrollData(newContext);
}

@marandaneto
Copy link
Member Author

@IchordeDionysos this is Flutter web only right?

@IchordeDionysos
Copy link

@marandaneto yes, given the way that Heatmaps are displayed and the need to build some mobile toolbar 🤔

The long term solution might work differently.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request Heatmap
Projects
None yet
Development

No branches or pull requests

3 participants