Skip to content

Commit

Permalink
feat: add flutterfire_tracker (#15)
Browse files Browse the repository at this point in the history
* feat: add flutterfire_tracker

* chore: fix Trackable class interface declaration.

* chore: refactor error tracking to use multiple trackers

* feat: add user ID tracking and clearing methods to
Trackable interface and Tracker class

* test: update test
  • Loading branch information
naipaka authored Nov 9, 2023
1 parent e555827 commit 108edd2
Show file tree
Hide file tree
Showing 11 changed files with 638 additions and 0 deletions.
1 change: 1 addition & 0 deletions .cspell/flutter-words.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ lerp
ltrb
LTWH
mockito
mocktail
nullsafety
Podfile
postbootstrap
Expand Down
30 changes: 30 additions & 0 deletions packages/flutterfire_tracker/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/

# IntelliJ related
*.iml
*.ipr
*.iws
.idea/

# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/

# Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/
.dart_tool/
.packages
build/
10 changes: 10 additions & 0 deletions packages/flutterfire_tracker/.metadata
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.

version:
revision: "d211f42860350d914a5ad8102f9ec32764dc6d06"
channel: "stable"

project_type: package
3 changes: 3 additions & 0 deletions packages/flutterfire_tracker/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 0.1.0

* initial release.
39 changes: 39 additions & 0 deletions packages/flutterfire_tracker/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<!--
This README describes the package. If you publish this package to pub.dev,
this README's contents appear on the landing page for your package.
For information about how to write a good package README, see the guide for
[writing package pages](https://dart.dev/guides/libraries/writing-package-pages).
For general information about developing packages, see the Dart guide for
[creating packages](https://dart.dev/guides/libraries/create-library-packages)
and the Flutter guide for
[developing packages and plugins](https://flutter.dev/developing-packages).
-->

TODO: Put a short description of the package here that helps potential users
know whether this package might be useful for them.

## Features

TODO: List what your package can do. Maybe include images, gifs, or videos.

## Getting started

TODO: List prerequisites and provide or point to information on how to
start using the package.

## Usage

TODO: Include short and useful examples for package users. Add longer examples
to `/example` folder.

```dart
const like = 'sample';
```

## Additional information

TODO: Tell users more about the package: where to find more information, how to
contribute to the package, how to file issues, what response they can expect
from the package authors, and more.
1 change: 1 addition & 0 deletions packages/flutterfire_tracker/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include: package:altive_lints/altive_lints.yaml
51 changes: 51 additions & 0 deletions packages/flutterfire_tracker/lib/src/trackable.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/// Abstract class that defines the interface for tracking events and errors.
///
/// Implementations of this class can send tracking information to various
/// analytics and monitoring services. This class should be implemented
/// by any service that wants to handle the application's tracking data,
/// such as events and errors.
abstract interface class Trackable {
/// Tracks an error.
///
/// Use this method to record errors that occur in the application.
/// You can mark an error as fatal by setting [fatal] to true.
///
/// Parameters:
/// [error] - The error object that was caught.
/// [stackTrace] - The stack trace associated with the error.
/// [fatal] - A flag to indicate if the error is fatal. Defaults to false.
Future<void> trackError(
dynamic error,
StackTrace? stackTrace, {
bool fatal = false,
});

/// Tracks an event.
///
/// This method allows for logging of custom events within the application.
/// Events can be anything from user actions to system events.
///
/// Parameters:
/// [name] - The name of the event to track.
/// [parameters] - Additional parameters or context to log with the event.
/// This is optional and can be null.
Future<void> trackEvent(
String name, {
Map<String, Object?>? parameters,
});

/// Sets the user ID.
///
/// This method allows for setting the user ID for the current user.
/// This is useful for associating events with a specific user.
///
/// Parameters:
/// [userId] - The user ID to set.
Future<void> setUserId(String userId);

/// Clears the user ID.
///
/// This method allows for clearing the user ID for the current user.
/// This is useful for dissociating events with a specific user.
Future<void> clearUserId();
}
171 changes: 171 additions & 0 deletions packages/flutterfire_tracker/lib/src/tracker.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import 'dart:async';
import 'dart:isolate';

import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:flutter/material.dart';

import 'trackable.dart';

/// A class that wraps a package for sending events to Analytics.
/// Its role is to "send necessary events to Analytics."
///
/// It exposes methods for sending analytic events and for configuration.
class Tracker {
Tracker({
FirebaseCrashlytics? crashlytics,
FirebaseAnalytics? analytics,
List<Trackable> trackers = const [],
}) : _crashlytics = crashlytics ?? FirebaseCrashlytics.instance,
_analytics = analytics ?? FirebaseAnalytics.instance,
_trackers = trackers;

final FirebaseCrashlytics _crashlytics;

final FirebaseAnalytics _analytics;

final List<Trackable> _trackers;

/// A callback for logging errors caught by the Flutter framework.
///
/// Usage example:
/// ```dart
/// FlutterError.onError = tracker.onFlutterError;
/// ```
///
Future<void> onFlutterError(
FlutterErrorDetails flutterErrorDetails, {
bool fatal = false,
}) async {
FlutterError.presentError(flutterErrorDetails);
await _crashlytics.recordFlutterError(flutterErrorDetails, fatal: fatal);
}

/// A callback to log asynchronous errors
/// that are not caught by the Flutter framework.
///
///
/// Usage example:
/// ```dart
/// PlatformDispatcher.instance.onError = tracker.onPlatformError;
/// ```
bool onPlatformError(Object error, StackTrace stack) {
unawaited(_crashlytics.recordError(error, stack, fatal: true));
for (final tracker in _trackers) {
unawaited(tracker.trackError(error, stack, fatal: true));
}
return true;
}

/// A listener to register with [Isolate.current]
/// to record errors outside of Flutter.
///
/// Usage example:
/// ```dart
/// Isolate.current.addErrorListener(
/// tracker.isolateErrorListener()
/// );
/// ```
SendPort isolateErrorListener() {
return RawReceivePort((List<dynamic> pair) async {
final errorAndStacktrace = pair;
await Future.wait([
_crashlytics.recordError(
errorAndStacktrace.first,
errorAndStacktrace.last as StackTrace,
fatal: true,
),
..._trackers.map(
(tracker) => tracker.trackError(
errorAndStacktrace.first,
errorAndStacktrace.last as StackTrace,
fatal: true,
),
),
]);
}).sendPort;
}

/// Record a Dart or native error that caused the application to crash.
Future<void> recordError(
Object exception,
StackTrace stack, {
bool fatal = false,
}) async {
await Future.wait([
_crashlytics.recordError(
exception,
stack,
fatal: fatal,
),
..._trackers.map(
(tracker) => tracker.trackError(
exception,
stack,
fatal: fatal,
),
),
]);
}

/// Set the user ID.
Future<void> setUserId(String userId) async {
await Future.wait([
_crashlytics.setUserIdentifier(userId),
_analytics.setUserId(id: userId),
..._trackers.map((tracker) => tracker.setUserId(userId)),
]);
}

/// Clear the set user ID.
Future<void> clearUserId() async {
await Future.wait([
_crashlytics.setUserIdentifier(''),
_analytics.setUserId(),
..._trackers.map((tracker) => tracker.clearUserId()),
]);
}

/// Set the user's properties.
Future<void> setUserProperties(Map<String, String?> properties) async {
for (final property in properties.entries) {
await _analytics.setUserProperty(
name: property.key,
value: property.value,
);
}
}

/// Returns a list of NavigatorObservers to register with Navigator.
/// Use [nameExtractor] to set the parameter value to send.
List<NavigatorObserver> navigatorObservers({
required String? Function(RouteSettings) nameExtractor,
}) {
return [
// Returns a NavigatorObserver of FirebaseAnalytics.
FirebaseAnalyticsObserver(
analytics: _analytics,
nameExtractor: nameExtractor,
),
];
}

/// Send an event to Analytics.
Future<void> logEvent(
String eventName, {
Map<String, Object?>? parameters,
}) async {
await Future.wait([
_analytics.logEvent(
name: eventName,
parameters: parameters,
),
..._trackers.map(
(tracker) => tracker.trackEvent(
eventName,
parameters: parameters,
),
),
]);
}
}
5 changes: 5 additions & 0 deletions packages/flutterfire_tracker/lib/tracker.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// flutterfire_tracker with FlutterFire Analytics and Crashlytics.
library flutterfire_tracker;

export 'src/trackable.dart';
export 'src/tracker.dart';
19 changes: 19 additions & 0 deletions packages/flutterfire_tracker/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: flutterfire_tracker
description: flutterfire_tracker with FlutterFire Analytics and Crashlytics.
publish_to: "none"
version: 0.1.0

environment:
sdk: ^3.0.0

dependencies:
firebase_analytics: ^10.6.3
firebase_crashlytics: ^3.4.3
flutter:
sdk: flutter

dev_dependencies:
altive_lints: ^1.8.1
flutter_test:
sdk: flutter
mocktail: ^1.0.1
Loading

0 comments on commit 108edd2

Please sign in to comment.