Skip to content

Commit 377b635

Browse files
authored
Add analytics layer (#104)
* add analytics layer * fix copilot review * add automatic tracking page
1 parent 4d7b528 commit 377b635

File tree

7 files changed

+214
-0
lines changed

7 files changed

+214
-0
lines changed

.github/instructions/copilot-agent.instructions.md.new

Whitespace-only changes.

.github/instructions/flutter.instructions.md.new

Whitespace-only changes.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import 'package:flutter/material.dart';
2+
3+
final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import 'package:app/main/init.dart';
2+
import 'package:app/presentation/ui/base/route_observer.dart';
3+
import 'package:common/analytics/abstract/analytics_client.dart';
4+
import 'package:flutter/material.dart';
5+
6+
/// A base class for pages that are tracked for analytics purposes.
7+
/// This class can be extended to implement specific tracking logic
8+
/// for different pages in the application.
9+
/// class HomePage extends TrackedPage {
10+
/// const HomePage({super.key});
11+
/// @override
12+
/// String get trackingName => "home_page";
13+
/// @override
14+
/// Map<String, dynamic>? get trackingProperties => {
15+
/// 'userType': 'guest',
16+
/// 'origin': 'splash_screen',
17+
/// };
18+
/// @override
19+
/// Widget buildPage(BuildContext context) {
20+
/// return Scaffold(
21+
/// appBar: AppBar(title: const Text("Home")),
22+
/// body: const Center(child: Text("Welcome")),
23+
/// );
24+
/// }
25+
///}
26+
abstract class TrackedPage extends StatefulWidget {
27+
const TrackedPage({super.key});
28+
29+
/// The name used for tracking this page.
30+
String get trackingName;
31+
32+
/// Automatic Events
33+
/// Override when needed
34+
bool get trackOnCreate => true;
35+
bool get trackOnEnter => true;
36+
bool get trackOnExit => true;
37+
bool get trackOnDispose => false;
38+
39+
/// Extra properties for tracking.
40+
Map<String, dynamic>? get trackingProperties => null;
41+
42+
/// Build normal
43+
Widget buildPage(BuildContext context);
44+
45+
@override
46+
State<TrackedPage> createState() => _TrackedPageState();
47+
}
48+
49+
class _TrackedPageState extends State<TrackedPage> with RouteAware {
50+
ModalRoute? _route;
51+
52+
AnalyticsClient get analytics => getIt<AnalyticsClient>();
53+
54+
void _track(String phase) {
55+
analytics.trackEvent('${widget.trackingName}_$phase',
56+
properties: widget.trackingProperties);
57+
}
58+
59+
@override
60+
void initState() {
61+
super.initState();
62+
WidgetsBinding.instance.addPostFrameCallback((_) {
63+
if (widget.trackOnCreate) _track("create");
64+
});
65+
}
66+
67+
@override
68+
void didChangeDependencies() {
69+
super.didChangeDependencies();
70+
_route = ModalRoute.of(context);
71+
if (_route is PageRoute) {
72+
routeObserver.subscribe(this, _route as PageRoute);
73+
}
74+
}
75+
76+
@override
77+
void dispose() {
78+
if (widget.trackOnDispose) _track("dispose");
79+
if (_route is PageRoute) {
80+
routeObserver.unsubscribe(this);
81+
}
82+
super.dispose();
83+
}
84+
85+
@override
86+
void didPush() {
87+
if (widget.trackOnEnter) _track("enter");
88+
}
89+
90+
@override
91+
void didPopNext() {
92+
if (widget.trackOnEnter) _track("enter");
93+
}
94+
95+
@override
96+
void didPushNext() {
97+
if (widget.trackOnExit) _track("exit");
98+
}
99+
100+
@override
101+
void didPop() {
102+
if (widget.trackOnExit) _track("exit");
103+
}
104+
105+
@override
106+
Widget build(BuildContext context) {
107+
return widget.buildPage(context);
108+
}
109+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import 'dart:async';
2+
3+
/// Additional method to track custom events with a specific type.
4+
/// Follow the naming convention for event types.
5+
/// Future trackInitLoginFlow() => trackEvent('init_login', properties: {...});
6+
/// Future trackErrorLogin() => trackEvent('error_login', properties: {...});
7+
8+
abstract class AnalyticsClient {
9+
/// Tracks an event with a function call and a name.
10+
/// This is useful for tracking events that are triggered by specific actions.
11+
/// Example usage:
12+
/// trackFunction(() => loginWithEmailPassword(email, password), 'login_triggered', properties: {email: email});
13+
Future trackFunction(
14+
FutureOr<void> Function() fn,
15+
String name, {
16+
Map<String, dynamic>? properties,
17+
});
18+
19+
Future trackEvent(String name, {Map<String, dynamic>? properties});
20+
21+
Future setUserId(String? userId);
22+
23+
Future setUserProperties(Map<String, dynamic> properties);
24+
25+
Future setUserProperty(String name, String value);
26+
27+
Future reset();
28+
29+
Future trackAppCreated();
30+
31+
Future trackAppUpdated();
32+
33+
Future trackAppDeleted();
34+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import 'dart:async';
2+
3+
import 'package:common/analytics/abstract/analytics_client.dart';
4+
5+
class FirebaseAnalytics implements AnalyticsClient {
6+
@override
7+
Future reset() {
8+
// TODO: implement reset
9+
throw UnimplementedError();
10+
}
11+
12+
@override
13+
Future setUserId(String? userId) {
14+
// TODO: implement setUserId
15+
throw UnimplementedError();
16+
}
17+
18+
@override
19+
Future setUserProperties(Map<String, dynamic> properties) {
20+
// TODO: implement setUserProperties
21+
throw UnimplementedError();
22+
}
23+
24+
@override
25+
Future setUserProperty(String name, String value) {
26+
// TODO: implement setUserProperty
27+
throw UnimplementedError();
28+
}
29+
30+
@override
31+
Future trackAppCreated() {
32+
// TODO: implement trackAppCreated
33+
throw UnimplementedError();
34+
}
35+
36+
@override
37+
Future trackAppDeleted() {
38+
// TODO: implement trackAppDeleted
39+
throw UnimplementedError();
40+
}
41+
42+
@override
43+
Future trackAppUpdated() {
44+
// TODO: implement trackAppUpdated
45+
throw UnimplementedError();
46+
}
47+
48+
@override
49+
Future trackEvent(String name, {Map<String, dynamic>? properties}) {
50+
// TODO: implement trackEvent
51+
throw UnimplementedError();
52+
}
53+
54+
@override
55+
Future trackFunction(
56+
FutureOr<void> Function() fn,
57+
String name, {
58+
Map<String, dynamic>? properties,
59+
}) =>
60+
Future.value(fn()).then((_) => trackEvent(name, properties: properties));
61+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
2+
class SetupAnalytics {
3+
static void initialize() {
4+
// Initialize analytics services here
5+
print("Analytics services initialized.");
6+
}
7+
}

0 commit comments

Comments
 (0)