DOCS · SDK · FLUTTER

tillpulse_flutter

Federated Flutter plugin (iOS + Android) for crash reporting, performance, and friction detection. Mirrors the React Native SDK's capability set in idiomatic Dart.

Install

# pubspec.yaml
dependencies:
  tillpulse_flutter: ^0.1.0

# Then:
flutter pub get

Initialise

Wrap your app inside TillPulse.init. The appRunnerargument runs your app inside a Zone that captures unhandled async errors.

import 'package:tillpulse_flutter/tillpulse_flutter.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await TillPulse.init(
    options: TillPulseOptions(
      dsn: 'https://<key>@ingest.tillpulse.io/<project-id>',
      release: '1.4.0',
      environment: 'production',

      enableAutoFlutterErrorTracking: true,
      enableNetworkTracking: true,
      enableFrictionDetection: true,
      enablePerformanceTracking: true,

      sendDefaultPii: false,
      maxBreadcrumbs: 100,
      maxQueueSize: 500,
      flushInterval: Duration(seconds: 30),

      beforeSend: (event) {
        if (event.exceptions?.first.value?.contains('PaymentCancelled') == true) {
          return null;
        }
        return event;
      },
    ),
    appRunner: () => runApp(const MyApp()),
  );
}

With enableAutoFlutterErrorTracking: true, the SDK installs hooks on FlutterError.onError, PlatformDispatcher.instance.onError, and isolate uncaught-error listeners.

Identify users

TillPulse.instance.setUser(TillPulseUser(
  id: hashedUserId,
  data: {'segment': 'pro', 'region': 'ng'},
));

TillPulse.instance.clearUser();

Tags & context

TillPulse.instance.setTag('region', 'ke');
TillPulse.instance.setTag('network_type', '4g');

TillPulse.instance.setContext('device', {
  'model': 'Tecno Spark 10',
  'ram_mb': 4096,
});

Manual capture

try {
  await processPayment();
} catch (exception, stackTrace) {
  await TillPulse.instance.captureException(
    exception,
    stackTrace: stackTrace,
    tags: {'flow': 'payment'},
    extras: {'amount': 5000, 'currency': 'KES'},
  );
}

await TillPulse.instance.captureMessage(
  'Payment validation failed',
  level: SeverityLevel.warning,
);

Breadcrumbs

TillPulse.instance.addBreadcrumb(Breadcrumb(
  category: 'payment',
  message: 'Initiated M-Pesa STK push',
  level: SeverityLevel.info,
  data: {'phone': '+254xxxxxxxx', 'amount': 5000},
));

Navigator observer

MaterialApp(
  navigatorObservers: [TillPulseNavigatorObserver()],
  // ...
)

Performance spans

final tx = TillPulse.instance.startTransaction(
  name: 'checkout', op: 'flow',
);

try {
  await charge();
  await tx.finish(status: SpanStatus.ok);
} catch (e) {
  await tx.finish(status: SpanStatus.internalError);
  rethrow;
}

// Or wrap a body:
await TillPulse.instance.withTransaction<void>(
  name: 'checkout',
  op: 'flow',
  body: () async { /* ... */ },
);

Error boundary

TillPulseErrorBoundary(
  fallback: const ErrorFallbackWidget(),
  child: const PaymentScreen(),
)

Native crash handlers

The plugin installs NSSetUncaughtExceptionHandler on iOS and Thread.setDefaultUncaughtExceptionHandler on Android, plus an OOM heuristic via SharedPreferences (records an "alive" marker on init and clears on graceful close — a missing marker on next launch indicates the process was killed unexpectedly).

Offline queue

await TillPulse.instance.flush();
final size = await TillPulse.instance.getQueueSize();

Persisted to shared_preferences. Bounded by event count + total payload bytes. Backoff is exponential, max 6 attempts.

Platform setup

Android

// android/app/build.gradle
android {
  defaultConfig {
    minSdkVersion 21
  }
}

If you ProGuard / R8:

# proguard-rules.pro
-keep class io.tillpulse.** { *; }
-keepattributes SourceFile,LineNumberTable
-keepattributes *Annotation*

iOS

Minimum deployment target: iOS 13.0. dSYMs are required for symbolicated stack traces. Add an Xcode Run Script phase to upload them on every release build, or use the tillpulse CLI.

Troubleshooting

  • Missing frames on Android → upload mapping.txt
  • iOS frames show <redacted> → upload the .dSYM.zip
  • Events not arriving → set debug: true, check Flutter console
  • Errors in compute() isolates → SDK auto-listens to isolate uncaught errors