DEV Community

Abdur Rafay Saleem
Abdur Rafay Saleem

Posted on

Implementing Logger and Server Analytics in Flutter

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')];
  }
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

This client has the following characteristics:

  1. Implement the AnalyticsClientBase class to be injectable later on.
  2. 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
}
Enter fullscreen mode Exit fullscreen mode

This client has the same characteristics as above:

  1. Implement the AnalyticsClientBase class to be injectable later on.
  2. 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:

  1. Consistent Interface: Both clients implement the same methods, ensuring consistent usage
  2. Implementation Freedom: Each client handles events in its own way while maintaining the contract
  3. Single Responsibility: Each client focuses on its specific analytics target
  4. Easy Testing: Mock implementations can be created for testing
  5. 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)