cxorbi_flutter 0.3.6
cxorbi_flutter: ^0.3.6 copied to clipboard
Cxorbi analytics for Flutter: session replay, heatmaps, screens, gestures, funnels, performance, error capture and in-app surveys for iOS and Android.
example/lib/main.dart
import 'dart:io' show Platform;
import 'package:cxorbi_flutter/cxorbi_flutter.dart';
import 'package:flutter/material.dart';
/// Cxorbi Flutter example + **manual Ask / Data test panel**.
///
/// Run against the local backend:
/// flutter run -d `<device>` \
/// --dart-define=CXORBI_SOURCE_TOKEN=`<org key>` \
/// --dart-define=CXORBI_API_URL=http://localhost:3004/api (iOS sim) \
/// or --dart-define=CXORBI_API_URL=http://`<mac-LAN-ip>`:3004/api (real Android)
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Cxorbi.init(CxorbiConfig(
apiKey: const String.fromEnvironment('CXORBI_SOURCE_TOKEN',
defaultValue: 'YOUR_PUBLIC_SOURCE_TOKEN'),
apiUrl: const String.fromEnvironment('CXORBI_API_URL',
defaultValue: 'https://api.cxorbi.com/api'),
environment: CxorbiEnvironment.development,
debugMode: true,
logLevel: LogLevel.debug,
surveysEnabled: true, // Ask layer on
));
await Cxorbi.instance.optIn();
runApp(const ExampleApp());
}
class ExampleApp extends StatelessWidget {
const ExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Cxorbi Ask + Data Test',
theme: ThemeData(useMaterial3: true, colorSchemeSeed: const Color(0xFF10B981)),
navigatorObservers: [CxorbiNavigatorObserver()],
// Mount the survey host once so Ask surveys present over the app.
builder: (context, child) => CxorbiSurveyHost(child: child!),
home: const TestPanel(),
);
}
}
class TestPanel extends StatefulWidget {
const TestPanel({super.key});
@override
State<TestPanel> createState() => _TestPanelState();
}
class _TestPanelState extends State<TestPanel> {
final List<String> _log = [];
String _userId = '(anonymous)';
String get _platform => Platform.isIOS ? 'ios' : (Platform.isAndroid ? 'android' : 'other');
@override
void initState() {
super.initState();
// Surface Ask lifecycle in the on-screen log.
Cxorbi.instance.setSurveyCallbacks(CxorbiSurveyCallbacks(
onSurveyDisplayed: (s) => _add('๐ survey displayed: ${s.title}'),
onQuestionAnswered: (s, qid, v) => _add('โ๏ธ answered โ $v'),
onSurveyClosed: (s) => _add('โ๏ธ survey closed (dismissed)'),
onSurveyCompleted: (s, a) => _add('โ
survey completed (${a.length} answers)'),
));
}
void _add(String msg) {
if (!mounted) return;
setState(() => _log.insert(0, msg));
}
Future<void> _showSurvey() async {
_userId = 'manual_${_platform}_tester';
Cxorbi.instance.identify(_userId);
await Cxorbi.instance.addUserProperties(properties: {
'ask_e2e': 'manual',
'plan': 'pro',
'risk_profile': 'aggressive',
'country': 'IN',
});
_add('๐ค identified $_userId + attributes set');
await Cxorbi.instance.showSurvey();
_add('๐ showSurvey() requested (platform=$_platform)');
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Ask + Data Test ยท $_platform')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text('User: $_userId', style: const TextStyle(fontWeight: FontWeight.w600)),
const SizedBox(height: 12),
_btn('๐ Show Ask Survey (all 11 types)', _showSurvey, primary: true),
_btn('๐ Track event: checkout_completed', () {
Cxorbi.instance.trackEvent(eventName: 'checkout_completed', properties: {'amount': 250, 'currency': 'INR'});
_add('๐ tracked checkout_completed');
}),
_btn('โญ Track event: add_to_watchlist', () {
Cxorbi.instance.trackEvent(eventName: 'add_to_watchlist', properties: {'symbol': 'RELIANCE'});
_add('โญ tracked add_to_watchlist');
}),
_btn('๐ท๏ธ Set attribute: vip = true', () async {
await Cxorbi.instance.addUserProperties(properties: {'vip': true});
_add('๐ท๏ธ set attribute vip=true');
}),
_btn('๐ค New anonymous user (reset)', () {
Cxorbi.instance.reset();
setState(() => _userId = '(anonymous)');
_add('๐ค identity reset');
}),
const SizedBox(height: 12),
const Text('Activity log', style: TextStyle(fontWeight: FontWeight.w600)),
const Divider(),
Expanded(
child: ListView(
children: [for (final l in _log) Padding(padding: const EdgeInsets.symmetric(vertical: 3), child: Text(l))],
),
),
],
),
),
);
}
Widget _btn(String label, VoidCallback onTap, {bool primary = false}) => Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: primary
? FilledButton(onPressed: onTap, child: Text(label))
: OutlinedButton(onPressed: onTap, child: Text(label)),
);
}