Skip to content

Commit

Permalink
chore: Allow overriding the route filtering using a ctor param `route…
Browse files Browse the repository at this point in the history
…Filter` (#95)
  • Loading branch information
marandaneto authored Apr 3, 2024
1 parent f5b8658 commit 8e3a6f3
Show file tree
Hide file tree
Showing 7 changed files with 185 additions and 15 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ jobs:
working-directory: ./example
run: flutter pub get

- name: Test
run: flutter test

- name: Build iOS
working-directory: ./example
run: flutter build ios --simulator --no-codesign
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
## Next

- chore: Allow overriding the route filtering using a ctor param `routeFilter` ([#95](https://github.com/PostHog/posthog-flutter/pull/95))

```dart
bool myRouteFilter(Route<dynamic>? route) =>
route is PageRoute || route is OverlayRoute;
final observer = PosthogObserver(routeFilter: myRouteFilter);
```

## 4.3.0

- add PrivacyInfo ([#94](https://github.com/PostHog/posthog-flutter/pull/94))
Expand Down
2 changes: 1 addition & 1 deletion lib/src/posthog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ class Posthog {
static PosthogFlutterPlatformInterface get _posthog =>
PosthogFlutterPlatformInterface.instance;

static final Posthog _instance = Posthog._internal();
static final _instance = Posthog._internal();

factory Posthog() {
return _instance;
Expand Down
55 changes: 42 additions & 13 deletions lib/src/posthog_observer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,47 +4,76 @@ import 'posthog.dart';

typedef ScreenNameExtractor = String? Function(RouteSettings settings);

/// [PostHogRouteFilter] allows to filter out routes that should not be tracked.
///
/// By default, only [PageRoute]s are tracked.
typedef PostHogRouteFilter = bool Function(Route<dynamic>? route);

String? defaultNameExtractor(RouteSettings settings) => settings.name;

class PosthogObserver extends RouteObserver<PageRoute<dynamic>> {
PosthogObserver({ScreenNameExtractor nameExtractor = defaultNameExtractor})
: _nameExtractor = nameExtractor;
bool defaultPostHogRouteFilter(Route<dynamic>? route) => route is PageRoute;

class PosthogObserver extends RouteObserver<ModalRoute<dynamic>> {
PosthogObserver(
{ScreenNameExtractor nameExtractor = defaultNameExtractor,
PostHogRouteFilter routeFilter = defaultPostHogRouteFilter})
: _nameExtractor = nameExtractor,
_routeFilter = routeFilter;

final ScreenNameExtractor _nameExtractor;

void _sendScreenView(PageRoute<dynamic> route) {
String? screenName = _nameExtractor(route.settings);
if (screenName != null) {
final PostHogRouteFilter _routeFilter;

bool _isTrackeableRoute(String? name) {
return name != null && name.trim().isNotEmpty;
}

void _sendScreenView(Route<dynamic>? route) {
if (route == null) {
return;
}

var screenName = _nameExtractor(route.settings);
if (_isTrackeableRoute(screenName)) {
// if the screen name is the root route, we send it as root ("/") instead of only "/"
if (screenName == '/') {
screenName = 'root (\'/\')';
}

Posthog().screen(screenName: screenName);
Posthog().screen(screenName: screenName!);
}
}

@override
void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
super.didPush(route, previousRoute);
if (route is PageRoute) {
_sendScreenView(route);

if (!_routeFilter(route)) {
return;
}

_sendScreenView(route);
}

@override
void didReplace({Route<dynamic>? newRoute, Route<dynamic>? oldRoute}) {
super.didReplace(newRoute: newRoute, oldRoute: oldRoute);
if (newRoute is PageRoute) {
_sendScreenView(newRoute);

if (!_routeFilter(newRoute)) {
return;
}

_sendScreenView(newRoute);
}

@override
void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
super.didPop(route, previousRoute);
if (previousRoute is PageRoute && route is PageRoute) {
_sendScreenView(previousRoute);

if (!_routeFilter(previousRoute)) {
return;
}

_sendScreenView(previousRoute);
}
}
4 changes: 3 additions & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ dependencies:
plugin_platform_interface: ^2.0.2

dev_dependencies:
flutter_lints: ^2.0.0
flutter_lints: ^3.0.0
flutter_test:
sdk: flutter

# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
Expand Down
13 changes: 13 additions & 0 deletions test/posthog_flutter_platform_interface_fake.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import 'package:posthog_flutter/src/posthog_flutter_platform_interface.dart';

class PosthogFlutterPlatformFake extends PosthogFlutterPlatformInterface {
String? screenName;

@override
Future<void> screen({
required String screenName,
Map<String, Object>? properties,
}) async {
this.screenName = screenName;
}
}
115 changes: 115 additions & 0 deletions test/posthog_observer_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:posthog_flutter/src/posthog_flutter_io.dart';
import 'package:posthog_flutter/src/posthog_flutter_platform_interface.dart';
import 'package:posthog_flutter/src/posthog_observer.dart';

import 'posthog_flutter_platform_interface_fake.dart';

void main() {
PageRoute<dynamic> route(RouteSettings? settings) => PageRouteBuilder<void>(
pageBuilder: (_, __, ___) => Container(),
settings: settings,
);

final fake = PosthogFlutterPlatformFake();

setUp(() {
TestWidgetsFlutterBinding.ensureInitialized();
PosthogFlutterPlatformInterface.instance = fake;
});

tearDown(() {
fake.screenName = null;
PosthogFlutterPlatformInterface.instance = PosthogFlutterIO();
});

PosthogObserver getSut(
{ScreenNameExtractor nameExtractor = defaultNameExtractor,
PostHogRouteFilter routeFilter = defaultPostHogRouteFilter}) {
return PosthogObserver(
nameExtractor: nameExtractor, routeFilter: routeFilter);
}

test('returns current route name', () {
final currentRoute = route(const RouteSettings(name: 'Current Route'));

final sut = getSut();
sut.didPush(currentRoute, null);

expect(fake.screenName, 'Current Route');
});

test('returns overriden route name', () {
final currentRoute = route(const RouteSettings(name: 'Current Route'));

String? nameExtractor(RouteSettings settings) => 'overriden';

final sut = getSut(nameExtractor: nameExtractor);
sut.didPush(currentRoute, null);

expect(fake.screenName, 'overriden');
});

test('returns overriden root route name', () {
final currentRoute = route(const RouteSettings(name: '/'));

final sut = getSut();
sut.didPush(currentRoute, null);

expect(fake.screenName, 'root (\'/\')');
});

test('does not capture not named routes', () {
final currentRoute = route(const RouteSettings(name: null));

final sut = getSut();
sut.didPush(currentRoute, null);

expect(fake.screenName, null);
});

test('does not capture blank routes', () {
final currentRoute = route(const RouteSettings(name: ' '));

final sut = getSut();
sut.didPush(currentRoute, null);

expect(fake.screenName, null);
});

test('does not capture filtered routes', () {
// CustomOverlawRoute isn't a PageRoute
final overlayRoute = CustomOverlawRoute(
settings: const RouteSettings(name: 'Overlay Route'),
);

final sut = getSut();
sut.didPush(overlayRoute, null);

expect(fake.screenName, null);
});

test('allows overriding the route filter', () {
final overlayRoute = CustomOverlawRoute(
settings: const RouteSettings(name: 'Overlay Route'),
);

bool defaultPostHogRouteFilter(Route<dynamic>? route) =>
route is PageRoute || route is OverlayRoute;

final sut = getSut(routeFilter: defaultPostHogRouteFilter);
sut.didPush(overlayRoute, null);

expect(fake.screenName, 'Overlay Route');
});
}

class CustomOverlawRoute extends OverlayRoute {
CustomOverlawRoute({super.settings});

@override
Iterable<OverlayEntry> createOverlayEntries() {
return [];
}
}

0 comments on commit 8e3a6f3

Please sign in to comment.