Lerix Flutter SDK

pub package Platforms License: MIT

The official Flutter SDK for Lerix, the developer monitoring and engagement platform. One integration gives you automatic crash and error reporting, push notifications, in-app feedback, NPS surveys and product analytics on iOS, Android, macOS, Windows and Web, all managed from the Lerix dashboard.

Full documentation lives at docs.lerix.dev. This README covers the essentials; the linked guides cover every option.

Features

Feature iOS macOS Android Web Windows Docs
Automatic capture of uncaught errors ✅ ✅ ✅ ✅ ✅ Error Tracking
Manual reporting with type, severity and metadata ✅ ✅ ✅ ✅ ✅ Error Tracking
Push notifications (APNs / FCM / Web Push / WNS) ✅ ✅ ✅ ✅ ✅ preview Push Notifications
Foreground, tap and cold-start tap callbacks ✅ ✅ ✅ ✅ Tap and cold-start tap Push Notifications
Topic subscriptions ✅ ✅ ✅ ✅ ✅ Push Notifications
Custom sounds and rich images ✅ Sounds only ✅ ✅ Images only Push Notifications
Update and unsend delivered notifications ✅ ✅ ✅ ✅ ❌ API reference
Anonymous per-install user registration ✅ ✅ ✅ ✅ ✅ Installation
In-app feedback with annotated screenshots ✅ ✅ ✅ ✅ ✅ In-app feedback
NPS survey ✅ ✅ ✅ ✅ ✅ NPS survey
Analytics: events, sessions, screens, installs ✅ ✅ ✅ ✅ ✅ Analytics

Android push works without a google-services.json file: the SDK configures Firebase Cloud Messaging at runtime from your project's settings.

Requirements

Minimum
Flutter 3.3.0
Dart 3.3.3
iOS 11.0
macOS 10.14
Android API 21 (Android 5.0)
Windows Windows 10 2004, with the Windows App Runtime 1.7
Web Any browser with Service Worker and Push API support

Installation

Add the package to your pubspec.yaml:

dependencies:
  lerix_flutter: ^0.4.0

Then run:

flutter pub get

You will need a project API key and project id. Both are shown on your project's settings page in the dashboard; see The Dashboard if you have not created a project yet.

Quick start

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

void main() {
  Lerix.init(
    url: 'https://api.lerix.dev/v1/YOUR_PROJECT_ID',
    apiKey: 'YOUR_API_KEY',
    projectId: 'YOUR_PROJECT_ID',
    builder: () async {
      WidgetsFlutterBinding.ensureInitialized();
      runApp(const MyApp());
    },
  );
}

Lerix.init runs your builder inside a guarded zone, so every uncaught Flutter and Dart error is reported without further code. It also registers the app and an anonymous user with your project. Push notifications are opt-in and are set up separately (see below).

Parameter Type Required Description
url String ✅ API base URL including your project id: https://api.lerix.dev/v1/<projectId>
apiKey String ✅ Project API key
projectId String ✅ Project id
builder void Function() ✅ App startup callback; call runApp here
debugMode bool? Print SDK activity to the console
onError Function(ErrorModel)? Called with every report the SDK sends
ignoreErrors List<Pattern> Errors to drop before sending (see Filtering errors)
beforeSend LerixErrorEvent? Function(LerixErrorEvent)? Inspect, change or drop each report before it is sent
environment String? dev, staging, prod... Defaults to dev for debug builds, simulators, emulators and localhost, otherwise prod
analytics LerixAnalyticsOptions enabled, autoSessions, autoScreens (all true by default)

Error tracking

Uncaught errors are reported automatically once Lerix.init has run. Report a handled exception yourself with:

try {
  await riskyOperation();
} catch (e, stack) {
  await Lerix.throwError(
    e,
    stack,
    bugType: BugType.crash,
    bugSeverity: BugSeverity.high,
    metaData: {'userId': currentUser.id, 'screen': 'checkout'},
  );
}
Parameter Type Required Description
exception dynamic ✅ The caught exception
stack StackTrace ✅ Its stack trace
bugType BugType runtimeError, logicBug, uiBug, networkError, performance, compatibility, validationError, security, crash, unknown
bugSeverity BugSeverity critical, high, medium, low, unknown
metaData Map<String, dynamic> Any extra context, shown with the report

Filtering errors

Two optional Lerix.init options decide which errors are sent. They apply to every report: uncaught Flutter, Dart and zone errors, Lerix.throwError, and web.

Lerix.init(
  // ...
  ignoreErrors: [
    'SocketException', // case-insensitive substring
    RegExp(r'^HttpException: 40[13]'), // RegExp, matched with hasMatch
  ],
  beforeSend: (event) {
    if (event.metadata?['test'] == true) return null; // drop it
    return event.copyWith(
      message: event.message.replaceAll(RegExp(r'token=\S+'), 'token=***'),
    );
  },
  builder: () => runApp(const MyApp()),
);
  • ignoreErrors runs first. A String drops any error whose message contains it, ignoring case (empty or whitespace-only strings are skipped). A RegExp drops any error whose message it matches.
  • beforeSend then gets a LerixErrorEvent (message, stack, type, severity, metadata). Return it to send it, return a changed copy from copyWith, or return null to drop it. If beforeSend throws, the original report is sent; the exception is never rethrown into your app.
  • A dropped report is not sent and doesn't call onError.

The SDK ignores nothing by default. Common Flutter noise (such as RenderFlex overflow) is filtered on the Lerix server.

Reports appear in the dashboard's Error Tracking module, where the Lerix AI assistant can explain a crash against your connected GitHub repository. See the Error Tracking guide and the module overview.

Push notifications

await Lerix.notifications.init();

Lerix.notifications.setOnNotificationReceived((payload) {
  // Arrived while the app was in the foreground
});

Lerix.notifications.setOnNotificationTapped((payload) {
  // The user tapped a notification, including taps that launched the app
  debugPrint(payload.metadata.toString());
});

final granted = await Lerix.notifications.requestPermissions();

// Pass this id in `deviceTokens` when sending from the dashboard or REST API.
final deviceId = await Lerix.notifications.getDeviceId();

Topics let a single notification reach every subscribed device:

await Lerix.notifications.subscribeToTopic('promotions');
await Lerix.notifications.unsubscribeFromTopic('promotions');
Method Returns Description
init() Future<void> Initialize the notifications module. Call this first.
requestPermissions() Future<bool> Ask the user for notification permission
checkPermissionStatus() Future<String?> Current permission status
getDeviceId() Future<String?> Lerix device id used to target this device
getDeviceToken() Future<String?> Raw APNs / FCM token or Web Push subscription
setOnNotificationReceived(cb) void Foreground notification handler
setOnNotificationTapped(cb) void Notification tap handler
subscribeToTopic(key) Future<bool> Subscribe this device to a topic
unsubscribeFromTopic(key) Future<bool> Leave a topic

Platform setup

iOS. Enable the Push Notifications capability in Xcode, upload your APNs key in the dashboard, and forward three AppDelegate callbacks to the SDK:

import lerix_flutter

override func application(_ application: UIApplication,
                          didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
  lerix_didRegisterForRemoteNotifications(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken)
}

override func application(_ application: UIApplication,
                          didFailToRegisterForRemoteNotificationsWithError error: Error) {
  lerix_didFailToRegisterForRemoteNotifications(application, didFailToRegisterForRemoteNotificationsWithError: error)
}

override func application(_ application: UIApplication,
                          didReceiveRemoteNotification userInfo: [AnyHashable: Any],
                          fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
  lerix_didReceiveRemoteNotification(application, didReceiveRemoteNotification: userInfo, fetchCompletionHandler: completionHandler)
}

Rich images require a Notification Service Extension target. A ready-made NotificationService.swift ships in ios/NotificationServiceExtension/. Step-by-step instructions, including the Xcode target setup, are in NOTIFICATIONS_SETUP_SIMPLE.md and APNS_SETUP_iOS.md.

macOS. Push goes through APNs with the same key you uploaded for iOS; the device registers with the macOS bundle ID. In Xcode, open macos/Runner:

  1. Signing & Capabilities: sign with your Apple team and add Push Notifications (adds com.apple.developer.aps-environment). An app signed "to run locally" can't receive pushes; macOS rejects the permission request.
  2. Make sure the App Sandbox allows Outgoing Connections (Client) (com.apple.security.network.client); the SDK needs it to reach Lerix.
  3. Forward the APNs callbacks in macos/Runner/AppDelegate.swift:
import lerix_flutter

override func application(_ application: NSApplication,
                          didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
  lerix_didRegisterForRemoteNotifications(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken)
}

override func application(_ application: NSApplication,
                          didFailToRegisterForRemoteNotificationsWithError error: Error) {
  lerix_didFailToRegisterForRemoteNotifications(application, didFailToRegisterForRemoteNotificationsWithError: error)
}

override func application(_ application: NSApplication,
                          didReceiveRemoteNotification userInfo: [String: Any]) {
  lerix_didReceiveRemoteNotification(application, didReceiveRemoteNotification: userInfo)
}

Windows (preview). Push goes through WNS with a Microsoft Entra ID app registration; add its tenant ID, client ID, client secret and object ID in the dashboard under Notifications → Settings → Windows. No Dart changes are needed: init() creates the push channel and registers the device. Ship Microsoft's Windows App Runtime 1.7 installer with your app. The first build downloads the Windows App SDK from NuGet, so it needs network access. A plain .exe receives notifications while the app is running; to receive them while it's closed, the app needs package identity (a sparse package). Full steps: Windows push notifications.

Linux isn't supported for push yet.

Android. No configuration is required. Custom notification sounds are resolved from android/app/src/main/res/raw/<name> by the name you choose in the dashboard.

Web. Copy web_sw/lerix-sw.js from this package into your app's web/ folder so it is served at /lerix-sw.js, then call init() and requestPermissions() as above. Generate the VAPID keys on your project's push settings page. The SDK registers the worker on its own /lerix/ scope, so it coexists with Flutter's flutter_service_worker.js; no --pwa-strategy flag is needed.

The complete guide, including sending from the dashboard and the REST API, is at Push Notifications.

User identity

Each install is registered as an anonymous project user. Read or reset it with:

String? userId = Lerix.getUserId();

// Recover from a stale or mismatched user, for example a device that was
// previously registered against a different Lerix project.
await Lerix.deleteUser();
await Lerix.reRegisterUser();

Call setUser after login with your own user id, and clearUser on logout. Your backend can then send to all of a user's devices with externalUserIds.

// After login
await Lerix.setUser(user.id, identityHash: hashFromYourServer);

// On logout
await Lerix.clearUser();

The id is stored on the device and re-sent automatically if the install is registered again. identityHash is only needed when the project requires identity verification: it is the hex HMAC-SHA256 of the user id keyed with the project's identity secret. Compute it on your server and pass it to the app; never ship the identity secret in the app.

In-app feedback

Wrap your app in LerixFeedback (above MaterialApp), then open the flow from anywhere below it:

runApp(const LerixFeedback(child: MyApp()));

// e.g. from a "Send feedback" button
final result = await Lerix.feedback.show(context);
if (result.submitted) print('Thanks! ${result.id}');

The app shrinks into a backdrop (keeping its state) and the user goes through message → labels → screenshots → email → a summary with Show details, which lists every field that will be sent. Screenshots are taken by navigating the app and tapping Capture, then annotated with a pen (4 colours, 3 sizes, undo/redo). Labels, the email mode and the screenshot mode come from the dashboard (cached for 24 hours); override them per call:

await Lerix.feedback.show(
  context,
  const LerixFeedbackOptions(
    labels: ['bug', 'improvement'],
    email: LerixEmailMode.required,
    custom: {'plan': 'pro'},
  ),
);
  • Lerix.feedback.setUserEmail('user@example.com') prefills the email field.
  • Lerix.feedback.submit(message: ..., screenshots: [pngBytes]) sends feedback from your own UI.
  • Wrap sensitive widgets in LerixConfidential(child: ...): they are painted over in screenshots.
  • Feedback is stored on the device first and retried until the server accepts it, so nothing is lost offline. The last 50 breadcrumbs (screen changes and Lerix.addBreadcrumb(...)) and the current session are attached.

The flow follows your theme (light/dark, primary colour); pass LerixFeedback(theme: LerixThemeData(...)) to override it. English and Arabic (right to left) are built in; add or override strings with LerixFeedback(strings: {'fr': LerixStrings.en.copyWith(send: 'Envoyer', ...)}). Other languages fall back to English.

NPS survey

// At a calm moment, for example on the home screen
await Lerix.surveys.maybeShowNps(context);

// While testing: skip the eligibility rules
await Lerix.surveys.maybeShowNps(context, force: true);

The survey (question, follow-up and thank-you text per group, sampling and cadence) is configured in the dashboard. The SDK only shows it when the install is eligible: at least minAppStarts launches, initialDelayDays after the first launch, inside the sampleRate, and not again until frequencyDays after an answer or dismissCooldownDays after a dismissal. With debugMode on, the reason a survey isn't shown is logged.

Analytics

Lerix.track('checkout_started', {'items': 3, 'coupon': 'EID'});
Lerix.screen('Checkout'); // manual screen view

MaterialApp(navigatorObservers: [LerixNavigatorObserver()]);
GoRouter(observers: [LerixNavigatorObserver()], routes: [...]);

Installs ($first_open), sessions ($session_start / $session_end, a new session after 30 minutes in the background), app updates ($app_update) and screen views ($screen_view, from route names) are recorded automatically. Events are stored on the device and sent in batches. Lerix.track never throws and can be called before Lerix.init.

await Lerix.analytics.setEnabled(false); // opt out (persisted, clears the queue)
await Lerix.analytics.flush();           // send now

Example app

example/ is a runnable app that exercises initialization, manual error reporting, user management, push notifications, the feedback flow, the NPS survey and analytics. Replace the placeholder key and project id in example/lib/main.dart before running it.

Sending notifications from your backend

Every action available in the dashboard is also exposed over HTTPS: send, update, unsend, cancel a scheduled notification and query per-device delivery status. See the API reference and the error and success codes.

AI coding agents can drive your account through the Lerix MCP server; see Connect an AI agent.

Resources

License

Released under the MIT License. See LICENSE.