Skip to main content

Flutter Mobile Observability

Mobile apps face observability challenges that backend services do not: devices run on battery, connectivity is unreliable, and the OS can kill your app at any time. OpenTelemetry gives you traces, metrics, and error data from your Flutter app exported to any OTLP-compatible collector.

This guide walks you through choosing an instrumentation approach, adding dependencies, wiring telemetry into your app, and verifying that spans reach your collector.

Time to Complete​

15-20 minutes

What You'll Accomplish​

  • Choose between automatic RUM and manual SDK instrumentation
  • Add OpenTelemetry dependencies to your Flutter project
  • Initialize telemetry in your app entry point
  • Verify spans are flowing to your collector

Prerequisites​

  • Flutter SDK 3.32.0+ and Dart SDK 3.9.2+ installed
  • A running OpenTelemetry Collector with an OTLP endpoint (see Docker Compose Setup for local development)
  • An existing Flutter app to instrument

Telemetry Architecture​

Before instrumenting your app, decide how telemetry gets from the device to Base14. There are two deployment models.

Flutter RUM Architecture

Devices send OTLP data to a load balancer or API gateway, which handles authentication and rate limiting. The gateway forwards traffic to an OTel collector that applies server-side sampling before exporting to Base14.

Alternative: Direct to Base14​

Devices send OTLP data directly to the Base14 ingestion endpoint, authenticating with OAuth tokens that the app manages.

Comparison​

Collector + API GatewayDirect to Base14
AuthenticationAPI gateway handles auth centrally — app only needs an API key or static tokenApp must manage OAuth token lifecycle (acquire, refresh, retry on 401)
Credential securityCredentials stay on your infra — app ships a lightweight key that the gateway validatesOAuth client secret must be embedded or fetched at runtime — risk of extraction from APK/IPA
Rate limitingAPI gateway enforces per-device or per-app rate limits — protects backend from traffic spikesNo rate limiting — a buggy release can flood Base14 with telemetry
Server-side samplingOTel collector applies tail sampling (e.g. keep all errors, sample 10% of healthy spans) — reduces cost without losing signalAll sampling must happen on-device — you lose the ability to make sampling decisions with full context
Buffering and retryCollector buffers and retries on export failure — device fire-and-forgetApp must handle retry logic and local buffering if Base14 is unreachable
Schema evolutionCollector processors can rename attributes, drop PII, or add resource attributes without app updatesAny schema change requires an app release and user update
Network efficiencyGateway can terminate TLS at the edge, compress, and batch — lower overhead per deviceEach device opens its own TLS connection to Base14 — more overhead at scale
Operational costRequires running a collector and gateway (but these are standard infra components)No additional infra to manage
Deployment complexityModerate — collector + gateway configLow — just configure the Base14 endpoint in the app
Best forProduction apps with multiple devices, teams that want control over sampling and PIIPrototypes, internal tools, or apps with a small user base
Recommendation

Use the Collector + API Gateway approach for production apps. The ability to rate limit, sample server-side, strip PII, and rotate credentials without app updates far outweighs the small infra cost. Reserve Direct to Base14 for prototypes or internal tools where simplicity matters more than control.

Step 1: Choose Your Approach​

Two instrumentation paths are available. Pick the one that fits your needs.

Flutterific RUMManual SDK
Packageflutterrific_opentelemetryopentelemetry
Session trackingAutomaticManual
Device/app/network contextAutomatic on every spanManual
Navigation spansAutomaticManual
Screen load/dwell timesAutomaticNot included
Cold start measurementAutomaticManual
Jank/ANR detectionAutomaticNot included
HTTP tracingVia RumHttpClient wrapperVia HttpService wrapper
W3C trace context propagationAutomatic (traceparent header)Automatic
Battery-aware samplingAutomatic (4-tier adaptive)Automatic
Breadcrumb trailAutomatic (last 20 actions on error spans)Not included
Error boundary widgetIncludedNot included
Flush on backgroundAutomatic (AppLifecycleListener)Manual
Conversion funnel trackingNot includedVia FunnelTrackingService
Custom spans and eventsSupportedSupported
Best forRUM dashboards, UX monitoringBackend correlation, fine-grained control

Decision guide: Use Flutterific RUM if you want session-level UX monitoring (jank, screen times, navigation, breadcrumbs, battery-aware sampling) with minimal code. Use the Manual SDK if you need conversion funnel tracking or full control over span creation and batching.

Full reference docs:

Step 2: Install Dependencies​

Add the packages for your chosen approach.

Flutterific RUM:

pubspec.yaml
dependencies:
flutterrific_opentelemetry: ^0.3.2
device_info_plus: ^11.0.0
package_info_plus: ^8.0.0
connectivity_plus: ^6.0.0
battery_plus: ^6.0.0

Manual SDK:

pubspec.yaml
dependencies:
opentelemetry: ^0.18.10
http: ^1.1.0
uuid: ^4.0.0
flutter_dotenv: ^6.0.0
device_info_plus: ^11.3.3

Then install:

flutter pub get

Step 3: Initialize Telemetry​

Both approaches initialize telemetry before runApp(). Below are the minimal entry points — see the reference docs for the complete file listings.

Flutterific RUM — create lib/main_otel.dart:

lib/main_otel.dart
import 'package:flutter/material.dart';
import 'package:flutterrific_opentelemetry/flutterrific_opentelemetry.dart';
import 'main.dart';
import 'otel/otel_config.dart';
import 'otel/rum_cold_start.dart';

Future<void> main() async {
RumColdStart.markMainStart();
await OTelConfig.initialize();
WidgetsBinding.instance.addObserver(OTelConfig.lifecycleObserver);
runApp(const MyApp());
RumColdStart.measureFirstFrame();
}

Note: The full lib/otel/ directory with all supporting files is documented in the Flutterific RUM reference.

Manual SDK — wrap your existing main() in runZonedGuarded:

lib/main.dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'services/telemetry_service.dart';

void main() {
runZonedGuarded(() async {
WidgetsFlutterBinding.ensureInitialized();
await TelemetryService.instance.initialize();
runApp(const MyApp());
}, (error, stack) {
TelemetryService.instance.recordCrash(error, stack);
});
}

Note: The full TelemetryService, MetricsService, and LogService implementations are documented in the Manual SDK reference.

Run the app with the appropriate entry point:

# Flutterific RUM
flutter run --target=lib/main_otel.dart

# Manual SDK (default entry point)
flutter run

Step 4: Verify Telemetry​

Once the app is running, confirm spans are reaching your collector.

  1. Check collector logs — look for incoming OTLP requests:

    docker logs otel-collector 2>&1 | grep -i "traces"
  2. Look for expected span names — depending on your approach:

    • Flutterific RUM: app.cold_start, navigation.push, screen.load, screen.dwell, jank.frame
    • Manual SDK: GET /api/products, screen_view, device.app.lifecycle
  3. Open Scout — navigate to the Traces view and filter by service.name = your-app-name. You should see spans arriving within 30 seconds of app activity.

Troubleshooting: If no spans appear, check that the OTEL_TRACE_ENDPOINT environment variable or hardcoded endpoint matches your collector's OTLP receiver address. See Troubleshooting Missing Telemetry Data for common issues.

Next Steps​

Was this page helpful?