evently_plus 2.1.1 copy "evently_plus: ^2.1.1" to clipboard
evently_plus: ^2.1.1 copied to clipboard

An independently maintained Flutter SDK for persistent event tracking and background-only analytics delivery.

Evently Plus #

pub package pub points License: MIT

An independently maintained Flutter event-tracking SDK with persistent local queuing and background-only remote delivery. Evently Plus is an unofficial fork of Evently.

โœจ Features #

  • ๐Ÿ—๏ธ Clean Architecture - Separation of concerns with clear layer boundaries
  • ๐Ÿ’พ Persistent Queue - Retains queued events across application restarts
  • โฐ Background-only Uploads - Uses best-effort Android and iOS background work
  • ๐Ÿšซ No Foreground Requests - Event logging only writes to the local queue
  • ๐ŸŒ Consumer-owned Endpoint - Sends to the exact endpoint and headers you configure
  • ๐Ÿ›ก๏ธ Error Handling - Comprehensive error handling with custom exceptions
  • ๐Ÿ“ Structured Logging - Configurable logging for debugging
  • ๐Ÿงช Automated Tests - Covers configuration, queuing, models, and navigation timing
  • ๐ŸŽฏ Type Safe - Strong typing with clear contracts

๐Ÿ“ฆ Installation #

Install the latest release from pub.dev:

flutter pub add evently_plus

๐Ÿš€ Quick Start #

1. Initialize the SDK #

Initialize Evently Plus in your main.dart before running your app:

import 'package:flutter/material.dart';
import 'package:evently_plus/evently_plus.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await EventlyClient.initialize(
    config: EventlyConfig(
      // This is the complete upload URL. Evently Plus appends no path.
      uploadEndpoint: Uri.parse(
        'https://analytics.example.com/v1/event-batches',
      ),
      requestHeaders: const {
        'Authorization': 'Bearer your-api-key',
      },
      environment: 'production',
      debugMode: false,
    ),
  );

  runApp(MyApp());
}

2. Track Events #

Log events anywhere in your app:

// Simple event
await EventlyClient.instance.logEvent(
  name: 'button_click',
  screenName: 'HomeScreen',
);

// Event with properties
await EventlyClient.instance.logEvent(
  name: 'purchase_completed',
  screenName: 'CheckoutScreen',
  properties: {
    'product_id': '12345',
    'amount': 99.99,
    'currency': 'USD',
  },
  userId: 'user_123',
);

3. Track Time Spent on Screens #

Create one observer after Evently is initialized and inject it into your router:

final eventlyObserver = EventlyNavigatorObserver(
  client: EventlyClient.instance,
);

MaterialApp(
  navigatorObservers: [eventlyObserver],
);

The observer queues a SCREEN_TIME_SPENT event whenever a page stops being visible or the app leaves the foreground. Each event contains duration_milliseconds and duration_seconds. Dialogs, menus, and bottom sheets are ignored by default. For parameterized routes, provide a screenNameResolver that returns a stable name rather than a user- or content-specific path.

If your app owns analytics initialization or user identity, route measurements through its analytics service:

final eventlyObserver = EventlyNavigatorObserver(
  onScreenTime: (screenTime) => analyticsService.logScreenTime(screenTime),
);

โš™๏ธ Configuration #

EventlyConfig supports the following options:

EventlyConfig(
  uploadEndpoint: Uri.parse(                  // Required: complete upload URL
    'https://api.example.com/v1/events',
  ),
  requestHeaders: const {                     // Optional: auth/custom headers
    'Authorization': 'Bearer your-token',
  },
  environment: 'production',                  // Sent as X-Evently-Environment
  debugMode: false,                           // Enable diagnostic logging
  requestTimeout: const Duration(seconds: 30),
  enableBackgroundUpload: true,
  backgroundUploadFrequency: const Duration(hours: 1), // Android interval
)

Upload protocol #

The background worker sends an HTTP POST to uploadEndpoint exactly as provided. Evently Plus sets Content-Type: application/json, adds X-Evently-Environment, and merges the configured requestHeaders. A request body has this shape:

{
  "events": [
    {
      "id": "...",
      "event_name": "button_click",
      "occurred_at": "2026-08-05T12:00:00.000Z",
      "properties": {"button_id": "save"},
      "session_id": "...",
      "platform": "android",
      "app_version": "42",
      "release_market": "play_store"
    }
  ],
  "timestamp": "2026-08-05T12:01:00.000Z",
  "sdk_version": "2.1.1"
}

Any HTTP status from 200 through 299 is treated as success, after which those events are removed from the queue. Other statuses and network errors preserve the events and report failure to the operating system's background scheduler.

โฐ Periodic Background Upload #

Background upload is enabled by default. During app runtime, every event is persisted locally and no upload is attempted. A Workmanager isolate is the only code path that sends events. The package exposes no foreground upload API. The task runs only when a network connection is available and uses one unique task registration to avoid duplicate schedules.

Android requires no additional host-app setup. For iOS, enable the processing background mode, permit the identifier com.eventlyplus.backgroundUpload, and register that identifier from the host app's AppDelegate. The Dart backgroundUploadFrequency configures Android; set the corresponding iOS frequency in native code.

Mobile operating systems treat periodic background work as best-effort. Android may delay work because of battery or network constraints, and iOS does not guarantee exact execution times.

To turn scheduling off:

EventlyConfig(
  uploadEndpoint: Uri.parse('https://api.example.com/v1/events'),
  enableBackgroundUpload: false,
)

๐Ÿ”ง Advanced Usage #

Check Pending Events #

Get the count of events waiting for background upload:

final count = await EventlyClient.instance.getPendingEventCount();
print('Pending events: $count');

Clear Event Queue #

Remove all queued events:

await EventlyClient.instance.clearPendingEvents();

Custom Logger #

Provide your own logger implementation:

class CustomLogger implements EventlyLogger {
  @override
  void log(LogLevel level, String message, [dynamic error, StackTrace? stackTrace]) {
    // Your custom logging logic
  }

  // Implement other methods...
}

await EventlyClient.initialize(
  config: config,
  logger: CustomLogger(),
);

๐Ÿ—๏ธ Architecture #

Evently Plus follows Clean Architecture principles:

๐Ÿ“ lib/
โ”œโ”€โ”€ ๐Ÿ“ core/              # Core infrastructure
โ”‚   โ”œโ”€โ”€ config/           # Configuration
โ”‚   โ”œโ”€โ”€ error/            # Exceptions & failures
โ”‚   โ””โ”€โ”€ logging/          # Logging abstraction
โ”œโ”€โ”€ ๐Ÿ“ domain/            # Business logic
โ”‚   โ”œโ”€โ”€ entities/         # Core entities
โ”‚   โ””โ”€โ”€ repositories/     # Repository contracts
โ””โ”€โ”€ ๐Ÿ“ data/              # Data layer
    โ”œโ”€โ”€ models/           # Data models
    โ”œโ”€โ”€ datasources/      # Remote & local data sources
    โ””โ”€โ”€ repositories/     # Repository implementations

๐Ÿ”’ Security Best Practices #

  1. Never hardcode credentials - Inject them from your application's configuration
  2. Use HTTPS - Always use a secure upload endpoint outside local development
  3. Validate input - The SDK validates all events automatically
  4. Sanitize PII - Don't include sensitive personal information in events

When background upload is enabled, Evently Plus persists requestHeaders in SharedPreferences so a background isolate can reconstruct the request. This storage is not encrypted. Use a scoped, revocable credential rather than a high-value long-lived secret, and reinitialize the client after rotating it.

๐Ÿงช Testing #

Run tests:

flutter test

The SDK includes comprehensive tests for:

  • Configuration validation
  • Event creation and validation
  • Client initialization
  • Error handling
  • Logging

๐Ÿ“š Documentation #

  • Read the API reference.
  • See the example directory for a complete demo app.
  • Review the changelog before upgrading.

๐Ÿ”„ Migration from v1.x #

v2.0.0 is a breaking change with a complete rewrite. Key differences:

Old (v1.x): #

Evently().initialize(serverUrl: 'https://api.example.com');
Evently().logEvent('event', screenName: 'Screen', description: 'Desc');

New (v2.0.0): #

await EventlyClient.initialize(
  config: EventlyConfig(
    uploadEndpoint: Uri.parse('https://api.example.com/v1/events'),
  ),
);
await EventlyClient.instance.logEvent(
  name: 'event',
  screenName: 'Screen',
  properties: {'description': 'Desc'},
);

Benefits of v2: #

  • Async initialization for better performance
  • Configuration object for better organization
  • Properties map instead of simple description
  • Error handling with exceptions
  • Durable local queue and background-only delivery
  • Background-oriented architecture

๐Ÿ“„ License #

This project is licensed under the MIT License - see the LICENSE file for details.

๐Ÿค Contributing #

Contributions are welcome! Please feel free to submit a Pull Request.

๐Ÿ“ž Support #

For issues, feature requests, or questions, open a GitHub issue.


Based on the original Evently package.

1
likes
150
points
30
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

An independently maintained Flutter SDK for persistent event tracking and background-only analytics delivery.

License

MIT (license)

Dependencies

dartz, equatable, flutter, http, shared_preferences, uuid, workmanager

More

Packages that depend on evently_plus