obsly_flutter 0.3.4
obsly_flutter: ^0.3.4 copied to clipboard
Advanced Flutter SDK for comprehensive user behavior analytics, UI event tracking, automatic screenshot capture with debug tools, and GoRouter navigation support.
Obsly Flutter SDK - Advanced User Behavior Analytics #

π Now supports MaterialApp, CupertinoApp, GoRouter, and custom Flutter architectures!
Overview #
Obsly delivers comprehensive user behavior analytics and observability for iOS, Android and Web applications. This Flutter SDK works seamlessly with any Flutter application architecture and provides intuitive initialization patterns.
π₯ New Framework-Agnostic Features #
- β MaterialApp Support - Full backward compatibility, zero breaking changes
- β CupertinoApp Support - Native iOS styling preserved, no forced MaterialApp wrapper
- β Custom Architecture Support - Works with any Flutter widget hierarchy
- β GoRouter Integration - Full support for GoRouter with automatic detection and UI event handling
- β Advanced Navigation - Ready for go_router, auto_route, and custom navigation systems
- β
Dio HTTP Client Support - Simple
dio.addObsly()integration for Dio HTTP monitoring - β Intuitive Integration - Clean API patterns with enhanced flexibility
- β Conflict Prevention - Safe coexistence with existing crash reporting systems
Core Capabilities #
- Automatic UI, navigation, network, console and crash capture
- HTTP client monitoring (native
httppackage anddiopackage) - Optional screenshot capture on UI and rage-click events
- Performance transactions and steps
- Custom metrics (counter, gauge, histogram timer)
- Tags, context, session and user identification
For API parity details, see the browser SDK documentation in the repository browser/README.md.
Table of Contents #
- Installation
- Framework-Agnostic Initialization
- App Wrapping (Any Architecture)
- Framework Examples
- MaterialApp Integration
- CupertinoApp Integration
- Custom Architecture Integration
- Advanced Features
- Navigation Integration
- Dio HTTP Client Integration
- Error Handling & Conflict Prevention
- Routing and Navigation Support
- GoRouter Integration
- Traditional Navigator Support
- Navigation Events Captured
- Custom Navigation Solutions
- Troubleshooting Navigation Issues
- Configuration
- Developer Methods
- Manage Sessions
- Client Identification
- Application Identification
- Tag
- Screenshot
- Performance
- Metrics
- Application Context
- SDK Control
- Miscellaneous
- Debug tools
- Migration Guide
- Requirements
- Getting Help
- License
Installation #
Add the dependency:
dependencies:
obsly_flutter: ^0.2.0
Framework-Agnostic Initialization #
π₯ Quick Setup (Recommended) #
The new API provides intuitive initialization patterns that work with any Flutter architecture:
import 'package:flutter/material.dart';
import 'package:obsly_flutter/obsly_sdk.dart';
void main() async {
// π₯ Optional: Configure async error capture (conflict-safe)
ObslySDK.enableAsyncErrorCapture(
global: true,
preventConflicts: true, // Safe with existing error reporting systems
);
// π₯ Initialize with error capture
ObslySDK.runWithAsyncErrorCapture(() async {
WidgetsFlutterBinding.ensureInitialized();
await ObslySDK.instance.init(
const InitParameters(
obslyKey: 'YOUR_OBSLY_KEY',
instanceURL: 'https://api.obsly.io',
debugMode: true,
logLevel: LogLevel.debug,
config: ObslyConfig(
enableDebugTools: true,
enableScreenshotOnUi: true,
rageClick: RageClickConfig(
active: true,
screenshot: true,
screenshotPercent: 0.25,
),
),
),
);
runApp(MyApp()); // Works with ANY Flutter app!
});
}
App Wrapping (Any Architecture) #
Framework-Agnostic Wrapping #
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Your app - MaterialApp, CupertinoApp, or Custom
final yourApp = MaterialApp(
title: 'My App',
home: HomePage(),
);
// π― Framework-agnostic wrapping - auto-detects app type
return ObslySDK.instance.wrapApp(
app: yourApp,
enableDebugTools: true,
);
}
}
Framework Examples #
MaterialApp Integration (Backward Compatible) #
class MaterialBankingApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final materialApp = MaterialApp(
title: 'Banking App',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
),
initialRoute: '/',
routes: {
'/': (context) => LoginScreen(),
'/dashboard': (context) => DashboardScreen(),
'/transactions': (context) => TransactionsScreen(),
},
);
// β
All MaterialApp properties preserved exactly
return ObslySDK.instance.wrapApp(
app: materialApp,
enableDebugTools: true,
);
}
}
CupertinoApp Integration (iOS Native) #
class iOSBankingApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final cupertinoApp = CupertinoApp(
title: 'iOS Banking',
theme: CupertinoThemeData(
primaryColor: CupertinoColors.systemBlue,
),
home: CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text('Banking'),
),
child: iOSHomeScreen(),
),
);
// β
Native iOS styling preserved, no MaterialApp forced
return ObslySDK.instance.wrapApp(
app: cupertinoApp,
enableDebugTools: true,
);
}
}
Custom Architecture Integration #
class CustomArchitectureApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final customApp = Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.purple, Colors.blue],
),
),
child: CustomNavigationSystem(
child: MyCustomScreens(),
),
);
// β
No forced MaterialApp wrapper
return ObslySDK.instance.wrapApp(
app: customApp,
customNavigationProvider: CustomNavigationProvider(),
enableDebugTools: true,
);
}
}
Advanced Features #
Navigation Integration #
go_router Support
final GoRouter _router = GoRouter(
routes: [
GoRoute(path: '/', builder: (context, state) => HomeScreen()),
GoRoute(path: '/profile', builder: (context, state) => ProfileScreen()),
],
);
class GoRouterApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final app = MaterialApp.router(routerConfig: _router);
return ObslySDK.instance.wrapApp(
app: app,
customNavigationProvider: GoRouterProvider(), // π Coming soon
);
}
}
auto_route Support
@AutoRouterConfig()
class AppRouter extends _$AppRouter {
@override
List<AutoRoute> get routes => [
AutoRoute(page: HomeRoute.page, path: '/'),
AutoRoute(page: ProfileRoute.page, path: '/profile'),
];
}
class AutoRouteApp extends StatelessWidget {
final _appRouter = AppRouter();
@override
Widget build(BuildContext context) {
final app = MaterialApp.router(routerConfig: _appRouter.config());
return ObslySDK.instance.wrapApp(
app: app,
customNavigationProvider: AutoRouteProvider(), // π Coming soon
);
}
}
Error Handling & Conflict Prevention #
Coexistence with Other Error Reporting
void main() async {
// Initialize your existing error reporting system
await initializeExistingErrorReporting();
// Initialize Obsly with conflict prevention
ObslySDK.enableAsyncErrorCapture(
global: true,
preventConflicts: true, // π‘οΈ Safe coexistence
);
ObslySDK.runWithAsyncErrorCapture(() async {
await ObslySDK.instance.init(params);
runApp(MyApp());
});
}
Custom Error Handling
void main() async {
// Configure with custom error handler
ObslySDK.enableAsyncErrorCapture(
global: true,
captureIsolateErrors: false,
preventConflicts: true,
);
// Listen to async errors
ObslySDK.onAsyncError.listen((error) {
print('Custom handling: ${error.error}');
// Your custom logic here
});
ObslySDK.runWithAsyncErrorCapture(() async {
await ObslySDK.instance.init(params);
runApp(MyApp());
});
}
Advanced Setup (Manual Control) #
For cases requiring granular initialization control:
import 'package:flutter/widgets.dart';
import 'package:obsly_flutter/obsly_sdk.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
ObslySDK.run(() async {
try {
await ObslySDK.instance.init(
InitParameters(
obslyKey: 'YOUR_OBSLY_KEY',
instanceURL: 'https://api.obsly.com',
appName: 'MyFlutterApp',
appVersion: '1.0.0',
logLevel: LogLevel.error,
debugMode: false,
config: const ObslyConfig(
enableAutomaticCapture: true,
enableScreenshotOnUi: false,
enableRageClickScreenshot: false,
),
// sessionID: 'optional-custom-session-id',
),
);
runApp(const MyApp());
} catch (e) {
// App continues without Obsly if initialization fails
runApp(const MyApp());
}
});
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return ObslySDK.instance.wrapAppLegacy(
app: MaterialApp(
title: 'My App',
home: const HomePage(),
),
enableDebugTools: false, // set true to show the debug overlay
);
}
}
Dio HTTP Client Integration #
For applications using the popular Dio HTTP client, Obsly provides seamless integration with a simple extension API:
Basic Usage
import 'package:dio/dio.dart';
import 'package:obsly_flutter/obsly_sdk.dart';
class ApiClient {
final Dio dio;
ApiClient() : dio = Dio() {
dio.addObsly(); // β¨ One-liner integration
}
Future<Map<String, dynamic>> getUser(String id) async {
final response = await dio.get('/users/$id');
return response.data;
}
}
Advanced Configuration
class ApiService {
final Dio _publicApi;
final Dio _privateApi;
ApiService() :
_publicApi = Dio(BaseOptions(baseUrl: 'https://api.example.com')),
_privateApi = Dio(BaseOptions(baseUrl: 'https://internal.example.com')) {
// Monitor public API calls
_publicApi.addObsly();
// Private API without monitoring (skip addObsly())
_privateApi.interceptors.add(AuthInterceptor());
}
}
Features
- β Automatic Request/Response Capture: Monitors all Dio HTTP requests and responses
- β Error Tracking: Captures DioExceptions and HTTP errors
- β Timing Metrics: Records request duration and performance data
- β Duplicate Prevention: Intelligent detection prevents adding multiple Obsly interceptors
- β Zero Configuration: Works out of the box with existing Dio instances
- β Coexistence: Works alongside your existing Dio interceptors
Verification
final dio = Dio();
dio.addObsly();
// Check if monitoring is active (optional)
print('Obsly monitoring: ${dio.hasObslyMonitoring}'); // true
// Make requests - automatically monitored
await dio.get('https://api.example.com/data');
All HTTP requests made through Dio will now appear in your Obsly dashboard alongside native http package requests.
Configuration #
Init parameters (parity with JS Browser SDK):
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
ObslyKey |
String |
Yes | β | Authorization API key |
instanceURL |
String |
Yes | β | API server URL |
remoteConfigURL |
String? |
No | β | Remote config URL |
proEnv |
bool? |
No | β | Production environment flag |
appVersion |
String? |
No | β | App version |
appName |
String? |
No | β | App name |
logLevel |
String? |
No | "error" |
Allowed values: null, error, warn, log, debug |
config |
ObslyConfig? |
No | see below | Advanced configuration |
debugMode |
bool? |
No | false |
Enable debug overlay and verbose behavior |
sessionID |
String? |
No | β | Custom session ID on init |
ObslyConfig structure:
| Parameter | Type | Default | Description |
|---|---|---|---|
enableAutomaticCapture |
bool |
true |
Enable all automatic interceptors (UI, navigation, HTTP, console, crashes) |
enableDebugTools |
bool |
false |
Render floating debug tools when wrapping app |
logLevel |
LogLevel |
LogLevel.error |
Runtime log level (use setLogLevel for string-based) |
userId |
String? |
β | Pre-set user id |
appName |
String? |
β | Override app name |
appVersion |
String? |
β | Override app version |
enableScreenshotOnUi |
bool |
false |
Screenshot on UI and lifecycle events |
enableRageClickScreenshot |
bool |
false |
Screenshot subset for rage-click detection |
requestBlacklist |
List<String>? |
β | Wildcard URL blacklist for HTTP capture |
requestBodyWhitelist |
List<RequestBodyConfig>? |
β | URLs to capture request/response body on errors |
requestHeadersWhitelist |
List<RequestHeadersConfig>? |
β | URLs to capture whitelisted headers on errors |
rageClick |
RageClickConfig? |
β | Rage click configuration |
rateLimits |
RateLimits? |
β | Event type rate limits |
enableCrashes |
bool? |
β | Toggle crash events |
enableLifeCycleLog |
bool? |
β | Toggle lifecycle events |
enableRequestLog |
bool? |
β | Toggle request events |
enableTagger |
bool? |
β | Toggle tag events |
enablePerformance |
bool? |
β | Toggle performance events |
enableUI |
bool? |
β | Toggle UI events |
Rate Limit Configuration #
You can control the rate of captured events. Configure per-event-type limits inside ObslyConfig.rateLimits.
Event types:
- base, request, tag, console, ui, metric, error, performance, navigation
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
interval |
int |
1000 |
Interval in milliseconds for rate limiting |
trailing |
bool |
false |
Send trailing events after the rate limit period |
bucketSize |
int |
10 |
Max events to process within an interval (request: 20) |
emptyBucketDelay |
int |
1000 |
Delay in milliseconds before emptying the bucket |
rejectWhenBucketFull |
bool |
false |
If true, reject when full (console, error: true) |
Example:
final params = InitParameters(
ObslyKey: 'KEY',
instanceURL: 'https://api.url',
config: ObslyConfig(
rateLimits: const RateLimits(
error: RateLimitConfig(
interval: 2000,
bucketSize: 15,
trailing: true,
emptyBucketDelay: 2000,
rejectWhenBucketFull: false,
),
ui: RateLimitConfig(
bucketSize: 20,
emptyBucketDelay: 2000,
),
),
),
);
Request Headers Config #
Control which headers are captured for requests within a status range and URL pattern.
| Parameter | Type | Example | Description |
|---|---|---|---|
url |
String |
https://api.example.com/sensitive/* |
URL to capture headers (supports wildcards) |
fromStatus |
int |
400 |
Min HTTP status (inclusive) |
toStatus |
int |
599 |
Max HTTP status (inclusive) |
headers |
List<String> |
["content-type","x-request-id"] |
Specific headers to capture (supports wildcards) |
Example:
InitParameters(
ObslyKey: 'KEY',
instanceURL: 'https://api.url',
config: const ObslyConfig(
requestHeadersWhitelist: [
RequestHeadersConfig(
url: 'https://api.example.com/sensitive/*',
fromStatus: 400,
toStatus: 599,
headers: ['content-type', 'x-request-id'],
),
],
),
);
Request Body Config #
Capture request and/or response bodies on error ranges for matching URLs.
| Parameter | Type | Example | Description |
|---|---|---|---|
url |
String |
https://api.example.com/* |
URL to capture body (supports wildcards) |
fromStatus |
int |
400 |
Min HTTP status (inclusive) |
toStatus |
int |
599 |
Max HTTP status (inclusive) |
captureRequestBody |
bool |
true |
Capture request body |
captureResponseBody |
bool |
true |
Capture response body |
Example:
const config = ObslyConfig(
requestBodyWhitelist: [
RequestBodyConfig(
url: 'https://api.example.com/*',
fromStatus: 400,
toStatus: 599,
captureRequestBody: true,
captureResponseBody: true,
),
],
);
Wildcards #
Use wildcards in URL patterns and header names:
- URL:
https://example.com/*,https://*.example.com/*,*.example.com/* - Header:
*content*,content*,*type,x-*
Developer Methods #
All functions are available on ObslySDK.instance. For readability, performance and metrics are also exposed via namespaced getters Performance and Metrics.
Manage Sessions:
await ObslySDK.instance.closeCurrentSession();
await ObslySDK.instance.createNewSession('custom-session-id');
Client Identification:
await ObslySDK.instance.setUserID('user-123');
await ObslySDK.instance.setPersonID('person-456');
await ObslySDK.instance.setPassportID('passport-789');
await ObslySDK.instance.setContractID('contract-000');
Application Identification:
await ObslySDK.instance.setAppName('MyNewApp');
await ObslySDK.instance.setAppVersion('1.2.0');
Tag:
await ObslySDK.instance.addTag(
[Tag(key: 'user_tier', value: 'premium')],
'User Properties',
);
Screenshot:
await ObslySDK.instance.addScreenshot();
final base64Image = await ObslySDK.instance.getScreenshot();
Performance:
await ObslySDK.instance.Performance.startTransaction('DASHBOARD', 'Load Dashboard');
await ObslySDK.instance.Performance.startStep('Load Dashboard', 'DASHBOARD');
await ObslySDK.instance.Performance.finishStep('Load Dashboard', 'DASHBOARD');
await ObslySDK.instance.Performance.endTransaction('DASHBOARD');
Metrics:
ObslySDK.instance.Metrics.incCounter('CLICK_EVENT', 'FBL', 'OP', 'VIEW', 'OK');
ObslySDK.instance.Metrics.setGauge('CPU', 0.75, 'FBL', 'OP', 'VIEW', 'OK');
ObslySDK.instance.Metrics.startHistogramTimer('FETCH', 'FBL', 'OP', 'VIEW');
ObslySDK.instance.Metrics.endHistogramTimer('FETCH', 'FBL', 'OP', 'VIEW', 'OK');
Application Context:
await ObslySDK.instance.setView('HOME');
await ObslySDK.instance.setOperation('ONBOARDING');
await ObslySDK.instance.setFunctionalBlock('AUTH');
SDK Control:
await ObslySDK.instance.pauseTracker();
await ObslySDK.instance.resumeTracker();
await ObslySDK.instance.setRequestsBlacklist(['https://*.sensitive.com/*']);
await ObslySDK.instance.setLogLevel('warn'); // 'null' | 'error' | 'warn' | 'log' | 'debug'
final session = ObslySDK.instance.getSessionInfo();
Miscellaneous:
await ObslySDK.instance.addFeedback('5', 'Great experience');
Migration Guide #
Migrating to Framework-Agnostic SDK #
From v0.x.x to v1.0.0 (Zero Breaking Changes)
Your existing MaterialApp code works unchanged:
Before (Old SDK - Still Works):
void main() {
ObslySDK.run(() {
runApp(
ObslySDK.wrapApp(
app: MaterialApp(...),
obslyKey: 'your-key',
instanceURL: 'https://api.obsly.io',
),
);
});
}
After (New SDK - Recommended):
void main() async {
ObslySDK.runWithAsyncErrorCapture(() async {
WidgetsFlutterBinding.ensureInitialized();
await ObslySDK.instance.init(
const InitParameters(
obslyKey: 'your-key',
instanceURL: 'https://api.obsly.io',
),
);
runApp(MyApp());
});
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ObslySDK.instance.wrapApp(
app: MaterialApp(...), // Works exactly the same
);
}
}
Benefits of Migration
- β CupertinoApp Support - Add iOS native support
- β Custom Architecture - Use any Flutter widget hierarchy
- β Better Error Handling - Advanced async error capture
- β Conflict Prevention - Safe with existing crash reporting
- β Navigation Flexibility - Ready for go_router, auto_route
Migration Checklist
-
Update Initialization (Optional but recommended):
// Replace ObslySDK.run() with: ObslySDK.runWithAsyncErrorCapture(() async { await ObslySDK.instance.init(params); runApp(MyApp()); }); -
Update App Wrapping (Optional but recommended):
// Replace ObslySDK.wrapApp() with parameters with: return ObslySDK.instance.wrapApp(app: yourApp); -
Enable Conflict Prevention (If using other crash tools):
ObslySDK.enableAsyncErrorCapture(preventConflicts: true); -
Test Different Architectures (Optional):
// Try CupertinoApp return ObslySDK.instance.wrapApp(app: CupertinoApp(...)); // Or custom architecture return ObslySDK.instance.wrapApp(app: CustomWidget(...));
Routing and Navigation Support #
Obsly Flutter SDK provides comprehensive support for both traditional Navigator and modern routing solutions like GoRouter.
π GoRouter Integration #
The SDK automatically detects and integrates with GoRouter-based applications. When using MaterialApp.router or CupertinoApp.router, the SDK:
- β Auto-detects GoRouter configuration and initializes navigation tracking
- β Captures navigation events with route names and transitions
- β Handles UI events correctly even with GoRouter's navigation architecture
- β Maintains zero configuration - works out of the box
Example: GoRouter Setup
import 'package:go_router/go_router.dart';
import 'package:obsly_flutter/obsly_sdk.dart';
final GoRouter router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomePage(),
),
GoRoute(
path: '/profile',
builder: (context, state) => const ProfilePage(),
),
],
);
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ObslySDK.instance.wrapApp(
app: MaterialApp.router(
routerConfig: router,
title: 'My GoRouter App',
),
enableDebugTools: true, // Optional debug overlay
);
}
}
Manual GoRouter Initialization (Optional)
For advanced use cases, you can manually initialize GoRouter tracking:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await ObslySDK.instance.init(/* your config */);
// Optional: Manual GoRouter initialization
await ObslySDK.initializeGoRouter(router);
runApp(MyApp());
}
Traditional Navigator Support #
The SDK continues to provide full support for traditional Navigator-based applications:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ObslySDK.instance.wrapApp(
app: MaterialApp(
title: 'My Navigator App',
initialRoute: '/',
routes: {
'/': (context) => const HomePage(),
'/profile': (context) => const ProfilePage(),
},
),
);
}
}
Navigation Events Captured #
The SDK automatically captures the following navigation events:
- Route Changes: Push, pop, replace operations
- Route Names: Full path information and route identification
- Navigation Timing: Transition durations and timestamps
- Navigation Context: Previous and current route information
Custom Navigation Solutions #
For custom navigation implementations, you can integrate with Obsly's navigation tracking:
// Custom navigation provider
class CustomNavigationProvider extends NavigationProvider {
// Implement your custom navigation tracking
}
// Register your provider
ObslySDK.instance.wrapApp(
app: yourCustomApp,
customNavigationProvider: CustomNavigationProvider(),
);
Troubleshooting Navigation Issues #
If navigation events are not appearing:
- Verify GoRouter Version: Ensure you're using GoRouter 6.0.0 or later
- Check Debug Logs: Enable debug mode to see navigation initialization logs
- Manual Initialization: Try manual GoRouter initialization if auto-detection fails
// Enable debug mode for navigation troubleshooting
await ObslySDK.instance.init(
InitParameters(
debugMode: true, // Shows detailed navigation logs
logLevel: LogLevel.debug,
// ... other config
),
);
Debug tools #
Enable the overlay by passing enableDebugTools: true to wrapApp. The overlay lets you inspect captured events and screenshots during development.
Requirements #
- Flutter >= 3.0.0
- Dart >= 3.0.0
Getting Help #
Questions or feedback? Email us at info@obsly.tech.
License #
MIT License - see LICENSE file for details.
Support #
- π Documentation
- π Issues
- π¬ Discussions
Contributing #
Contributions are welcome! Please read our Contributing Guide for details.