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 getInitialise
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