Previous Article: Part 1: Building a Robust Analytics Architecture in Flutter
Now that we have our analytics foundation in place, let's implement two concrete analytics clients: a logger client for development and a server client for custom backend analytics.
App Logger Service
First we will setup our logging service using the logger package. Here is the code for a service wrapper around this package:
import 'package:clock/clock.dart';
import 'package:logger/logger.dart';
final appLogger = LogService.forClass('[DEBUG]', level: Level.debug);
class LogService {
final Logger _logger;
const LogService(this._logger);
LogService.forClass(
String className, {
Level? level,
}) : _logger = Logger(
printer: _SimpleLogPrinter(className),
level: level,
);
void debug(dynamic message, {StackTrace? stackTrace}) =>
_logger.d(message, stackTrace: stackTrace);
void info(dynamic message) => _logger.i(message);
void error(dynamic message) => _logger.e(message);
void warn(dynamic message) => _logger.w(message);
void verbose(dynamic message) => _logger.t(message);
void wtf(dynamic message) => _logger.f(message);
}
class _SimpleLogPrinter extends PrettyPrinter {
final String className;
_SimpleLogPrinter(this.className);
@override
List<String> log(LogEvent event) {
final level = event.level;
final color = PrettyPrinter.defaultLevelColors[level]!;
final emoji = PrettyPrinter.defaultLevelEmojis[level];
final message = event.message as Object?;
final date = clock.now();
var msg = '${date.year}/${date.month}/${date.day}';
msg += ' ${date.hour}:${date.minute}:${date.second}';
msg += ' $message';
return [color('$emoji $className - $msg')];
}
}
This wrapper allow nicely formatted messages to be logged to the console and also provides utility methods to log events of different priorities.
Finally there is a global appLogger
that we use for debugging purposes.
Logger Analytics Client
The LoggerAnalyticsClient
is a development-focused implementation that logs all analytics events to the console:
class LoggerAnalyticsClient implements AnalyticsClientBase {
const LoggerAnalyticsClient();
@override
Future<void> trackEvent(String eventName, [Map<String, Object>? eventData]) async {
appLogger.debug('trackEvent($eventName, $eventData)');
}
// ... other implementations
}
This client has the following characteristics:
- Implement the
AnalyticsClientBase
class to be injectable later on. - Override all the contract methods to provide own logic for logging using the
appLogger
.
This implementation serves several purposes:
- Development debugging and testing
- Documentation of analytics events in logs
- Verification of analytics integration during development
Benefits of Logger Analytics
- Immediate feedback during development
- No external service dependencies for testing
- Clear visibility of all tracked events
- Easy debugging of analytics implementation
The full code for the service is found here:
Server Analytics Client
The ServerAnalyticsClient
sends analytics data to your custom backend:
class ServerAnalyticsClient implements AnalyticsClientBase {
final ApiService _apiService;
const ServerAnalyticsClient(this._apiService);
@override
Future<void> trackEvent(String eventName, [Map<String, Object>? eventData]) async {
return _apiService.setData(
endpoint: '/analytics',
data: {
'event': eventName,
'data': eventData,
},
converter: (res) => res.headers.isSuccess,
);
}
// ... other implementations
}
This client has the same characteristics as above:
- Implement the
AnalyticsClientBase
class to be injectable later on. - Override all the contract methods to provide own logic for sending analytics to the backend using your own api service.
It uses my custom implementation of ApiService
that I have explained in detail in my Clean Flutter Networking Architecture series. Or you can customize it to use either the Dio or http package for simplicity.
Server Analytics Features
- Custom backend integration
- Full control over analytics data
- Consistent event format
- Error handling and response validation
- Dependency injection for API service
Here is the entire code for the client:
Design Pattern Benefits
Both implementations demonstrate the power of our base class approach:
- Consistent Interface: Both clients implement the same methods, ensuring consistent usage
- Implementation Freedom: Each client handles events in its own way while maintaining the contract
- Single Responsibility: Each client focuses on its specific analytics target
- Easy Testing: Mock implementations can be created for testing
- Open Closed Principle: New services can be added by simple implementation of the interface, without modifying existing code.
Coming Up Next
In Part 3: Implementing PostHog Analytics in Flutter, we'll integrate PostHog, a popular open-source analytics platform. We'll cover PostHog setup, configuration, and implementation details.
Top comments (0)