Configure Superpowers.observer for performance tracking, analytics, and debugging of all mix() calls
This skill configures Superpowers.observer for performance tracking, analytics, and debugging of all mix() calls.
Sets up a global observer that:
Set Superpowers.observer before runApp():
import 'package:bloc_superpowers/bloc_superpowers.dart';
import 'package:flutter/material.dart';
void main() {
Superpowers.observer = (
bool isStart,
Object key,
Object? metrics,
Object? error,
StackTrace? stackTrace,
Duration? duration,
) {
// Handle observation
};
runApp(
Superpowers(
child: MaterialApp(...),
),
);
}
| Parameter | When | Description |
|-----------|------|-------------|
| isStart | Always | true at start, false at end |
| key | Always | The key from mix() call |
| metrics | Always | Custom data from metrics callback |
| error | End only | Exception if operation failed |
| stackTrace | End only | Stack trace if operation failed |
| duration | End only | How long the operation took |
Superpowers.observer = (isStart, key, metrics, error, stackTrace, duration) {
if (isStart) {
print('▶ Starting: $key');
} else {
if (error != null) {
print('✗ Failed: $key after ${duration?.inMilliseconds}ms - $error');
} else {
print('✓ Completed: $key in ${duration?.inMilliseconds}ms');
}
}
};
Superpowers.observer = (isStart, key, metrics, error, stackTrace, duration) {
if (!isStart && duration != null) {
// Log slow operations
if (duration.inMilliseconds > 1000) {
print('⚠ Slow operation: $key took ${duration.inMilliseconds}ms');
}
// Send to analytics
analytics.logTiming(
category: 'cubit_operation',
name: key.toString(),
duration: duration,
success: error == null,
);
}
};
Superpowers.observer = (isStart, key, metrics, error, stackTrace, duration) {
if (!isStart) {
// Firebase Analytics
FirebaseAnalytics.instance.logEvent(
name: 'cubit_operation',
parameters: {
'key': key.toString(),
'duration_ms': duration?.inMilliseconds,
'success': error == null,
'error_type': error?.runtimeType.toString(),
},
);
// Or Amplitude
Amplitude.instance.logEvent(
'cubit_operation_completed',
eventProperties: {
'key': key.toString(),
'duration_ms': duration?.inMilliseconds,
'success': error == null,
},
);
}
};
Superpowers.observer = (isStart, key, metrics, error, stackTrace, duration) {
if (!isStart && error != null) {
// Send to Sentry
Sentry.captureException(
error,
stackTrace: stackTrace,
hint: Hint.withMap({
'key': key.toString(),
'duration_ms': duration?.inMilliseconds.toString(),
}),
);
// Or Crashlytics
FirebaseCrashlytics.instance.recordError(
error,
stackTrace,
reason: 'mix operation failed: $key',
);
}
};
Pass custom data to the observer with the metrics parameter:
void loadUser() => mix(
key: this,
metrics: () => state, // Pass current state
() async {
final user = await api.getUser();
emit(user);
},
);
// In observer
Superpowers.observer = (isStart, key, metrics, error, stackTrace, duration) {
if (metrics != null) {
print('State at ${isStart ? "start" : "end"}: $metrics');
}
};
void loadData() => mix(
key: this,
metrics: () => this, // Pass entire Cubit
() async {
final data = await api.getData();
emit(data);
},
);
// In observer - access Cubit state
Superpowers.observer = (isStart, key, metrics, error, stackTrace, duration) {
if (metrics is MyCubit) {
print('Cubit state: ${metrics.state}');
}
};
void processOrder(Order order) => mix(
key: this,
metrics: () => {
'orderId': order.id,
'itemCount': order.items.length,
'total': order.total,
},
() async {
await api.processOrder(order);
},
);
// In observer
Superpowers.observer = (isStart, key, metrics, error, stackTrace, duration) {
if (metrics is Map) {
analytics.logEvent('order_processing', parameters: metrics);
}
};
isStart: true), once at end (isStart: false)retry, observer only sees the overall start/end, not individual attemptsmetrics callback runs at both start and end, so you can see state changesvoid main() {
_setupObserver();
runApp(
Superpowers(
child: MyApp(),
),
);
}
void _setupObserver() {
Superpowers.observer = (
bool isStart,
Object key,
Object? metrics,
Object? error,
StackTrace? stackTrace,
Duration? duration,
) {
final keyStr = _formatKey(key);
if (isStart) {
// Log operation start
debugPrint('▶ [$keyStr] Starting');
} else {
// Log operation completion
final durationMs = duration?.inMilliseconds ?? 0;
if (error != null) {
debugPrint('✗ [$keyStr] Failed after ${durationMs}ms: $error');
// Report to crash analytics
_reportError(key, error, stackTrace, duration);
} else {
debugPrint('✓ [$keyStr] Completed in ${durationMs}ms');
// Report slow operations
if (durationMs > 2000) {
_reportSlowOperation(key, duration!);
}
}
// Send analytics
_sendAnalytics(key, duration, error == null);
}
};
}
String _formatKey(Object key) {
if (key is Type) return key.toString();
if (key is Record) return key.toString();
return key.runtimeType.toString();
}
void _reportError(Object key, Object error, StackTrace? stack, Duration? duration) {
FirebaseCrashlytics.instance.recordError(
error,
stack,
reason: 'Operation failed: $key',
information: ['Duration: ${duration?.inMilliseconds}ms'],
);
}
void _reportSlowOperation(Object key, Duration duration) {
FirebaseAnalytics.instance.logEvent(
name: 'slow_operation',
parameters: {
'key': key.toString(),
'duration_ms': duration.inMilliseconds,
},
);
}
void _sendAnalytics(Object key, Duration? duration, bool success) {
FirebaseAnalytics.instance.logEvent(
name: 'cubit_operation',
parameters: {
'key': key.toString(),
'duration_ms': duration?.inMilliseconds,
'success': success,
},
);
}
Reset the observer between tests:
setUp(() {
Superpowers.clear(); // Removes observer and resets state
});
Ask the user:
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer