-
Notifications
You must be signed in to change notification settings - Fork 0
Add analytics layer #104
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
base: main
Are you sure you want to change the base?
Add analytics layer #104
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import 'package:flutter/material.dart'; | ||
|
||
final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>(); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
import 'package:app/main/init.dart'; | ||
import 'package:app/presentation/ui/base/route_observer.dart'; | ||
import 'package:common/analytics/abstract/analytics_client.dart'; | ||
import 'package:flutter/material.dart'; | ||
|
||
/// A base class for pages that are tracked for analytics purposes. | ||
/// This class can be extended to implement specific tracking logic | ||
/// for different pages in the application. | ||
/// class HomePage extends TrackedPage { | ||
/// const HomePage({super.key}); | ||
/// @override | ||
/// String get trackingName => "home_page"; | ||
/// @override | ||
/// Map<String, dynamic>? get trackingProperties => { | ||
/// 'userType': 'guest', | ||
/// 'origin': 'splash_screen', | ||
/// }; | ||
/// @override | ||
/// Widget buildPage(BuildContext context) { | ||
/// return Scaffold( | ||
/// appBar: AppBar(title: const Text("Home")), | ||
/// body: const Center(child: Text("Welcome")), | ||
/// ); | ||
/// } | ||
///} | ||
abstract class TrackedPage extends StatefulWidget { | ||
const TrackedPage({super.key}); | ||
|
||
/// The name used for tracking this page. | ||
String get trackingName; | ||
|
||
/// Automatic Events | ||
/// Override when needed | ||
bool get trackOnCreate => true; | ||
bool get trackOnEnter => true; | ||
bool get trackOnExit => true; | ||
bool get trackOnDispose => false; | ||
|
||
/// Extra properties for tracking. | ||
Map<String, dynamic>? get trackingProperties => null; | ||
|
||
/// Build normal | ||
Widget buildPage(BuildContext context); | ||
|
||
@override | ||
State<TrackedPage> createState() => _TrackedPageState(); | ||
} | ||
|
||
class _TrackedPageState extends State<TrackedPage> with RouteAware { | ||
ModalRoute? _route; | ||
|
||
AnalyticsClient get analytics => getIt<AnalyticsClient>(); | ||
|
||
void _track(String phase) { | ||
analytics.trackEvent('${widget.trackingName}_$phase', | ||
properties: widget.trackingProperties); | ||
} | ||
|
||
@override | ||
void initState() { | ||
super.initState(); | ||
WidgetsBinding.instance.addPostFrameCallback((_) { | ||
if (widget.trackOnCreate) _track("create"); | ||
}); | ||
} | ||
|
||
@override | ||
void didChangeDependencies() { | ||
super.didChangeDependencies(); | ||
_route = ModalRoute.of(context); | ||
if (_route is PageRoute) { | ||
routeObserver.subscribe(this, _route as PageRoute); | ||
} | ||
} | ||
|
||
@override | ||
void dispose() { | ||
if (widget.trackOnDispose) _track("dispose"); | ||
if (_route is PageRoute) { | ||
routeObserver.unsubscribe(this); | ||
} | ||
super.dispose(); | ||
} | ||
|
||
@override | ||
void didPush() { | ||
if (widget.trackOnEnter) _track("enter"); | ||
} | ||
|
||
@override | ||
void didPopNext() { | ||
if (widget.trackOnEnter) _track("enter"); | ||
} | ||
|
||
@override | ||
void didPushNext() { | ||
if (widget.trackOnExit) _track("exit"); | ||
} | ||
|
||
@override | ||
void didPop() { | ||
if (widget.trackOnExit) _track("exit"); | ||
} | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
return widget.buildPage(context); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import 'dart:async'; | ||
|
||
/// Additional method to track custom events with a specific type. | ||
/// Follow the naming convention for event types. | ||
/// Future trackInitLoginFlow() => trackEvent('init_login', properties: {...}); | ||
/// Future trackErrorLogin() => trackEvent('error_login', properties: {...}); | ||
|
||
abstract class AnalyticsClient { | ||
/// Tracks an event with a function call and a name. | ||
/// This is useful for tracking events that are triggered by specific actions. | ||
/// Example usage: | ||
/// trackFunction(() => loginWithEmailPassword(email, password), 'login_triggered', properties: {email: email}); | ||
Future trackFunction( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Function type is too generic and doesn't provide type safety. Consider using FutureOr Function() or separate methods for sync/async functions to ensure proper type checking and runtime behavior. Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||
FutureOr<void> Function() fn, | ||
String name, { | ||
Map<String, dynamic>? properties, | ||
}); | ||
|
||
Future trackEvent(String name, {Map<String, dynamic>? properties}); | ||
|
||
Future setUserId(String? userId); | ||
|
||
Future setUserProperties(Map<String, dynamic> properties); | ||
|
||
Future setUserProperty(String name, String value); | ||
|
||
Future reset(); | ||
|
||
Future trackAppCreated(); | ||
|
||
Future trackAppUpdated(); | ||
|
||
Future trackAppDeleted(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import 'dart:async'; | ||
|
||
import 'package:common/analytics/abstract/analytics_client.dart'; | ||
|
||
class FirebaseAnalytics implements AnalyticsClient { | ||
@override | ||
Future reset() { | ||
// TODO: implement reset | ||
throw UnimplementedError(); | ||
} | ||
|
||
@override | ||
Future setUserId(String? userId) { | ||
// TODO: implement setUserId | ||
throw UnimplementedError(); | ||
} | ||
|
||
@override | ||
Future setUserProperties(Map<String, dynamic> properties) { | ||
// TODO: implement setUserProperties | ||
throw UnimplementedError(); | ||
} | ||
|
||
@override | ||
Future setUserProperty(String name, String value) { | ||
// TODO: implement setUserProperty | ||
throw UnimplementedError(); | ||
} | ||
|
||
@override | ||
Future trackAppCreated() { | ||
// TODO: implement trackAppCreated | ||
throw UnimplementedError(); | ||
} | ||
|
||
@override | ||
Future trackAppDeleted() { | ||
// TODO: implement trackAppDeleted | ||
throw UnimplementedError(); | ||
} | ||
|
||
@override | ||
Future trackAppUpdated() { | ||
// TODO: implement trackAppUpdated | ||
throw UnimplementedError(); | ||
} | ||
|
||
@override | ||
Future trackEvent(String name, {Map<String, dynamic>? properties}) { | ||
// TODO: implement trackEvent | ||
throw UnimplementedError(); | ||
} | ||
|
||
@override | ||
Future trackFunction( | ||
FutureOr<void> Function() fn, | ||
String name, { | ||
Map<String, dynamic>? properties, | ||
}) => | ||
Future.value(fn()).then((_) => trackEvent(name, properties: properties)); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
|
||
class SetupAnalytics { | ||
static void initialize() { | ||
// Initialize analytics services here | ||
print("Analytics services initialized."); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using print() statements for logging is not recommended in production code. Consider using a proper logging framework or removing debug prints before production deployment. Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The example shows tracking email addresses in analytics properties, which could be sensitive user data. Consider using hashed identifiers or removing PII from analytics examples to avoid accidental data exposure.
Copilot uses AI. Check for mistakes.