tracebird_flutter 0.1.0
tracebird_flutter: ^0.1.0 copied to clipboard
Tester feedback for Flutter apps: shake to report, annotate the screen, and every report ships with logs, failed requests and device context.
import 'package:flutter/material.dart';
import 'package:tracebird_flutter/tracebird_flutter.dart';
import 'package:webview_flutter/webview_flutter.dart';
/// A deliberately broken checkout, so there is something worth reporting.
Future<void> main() async {
await Tracebird.init(
const TracebirdOptions(
projectKey: String.fromEnvironment('TRACEBIRD_KEY', defaultValue: 'pk_live_site_dev'),
endpoint: String.fromEnvironment(
'TRACEBIRD_ENDPOINT',
defaultValue: 'https://api.tracebird.dev',
),
environment: 'staging',
release: '1.4.2',
tester: 'Sam K.',
trigger: TracebirdTrigger.floatingButton,
verbose: true,
),
);
runApp(const TracebirdWrapper(child: ExampleApp()));
}
/// The example application.
class ExampleApp extends StatelessWidget {
/// Creates the example app.
const ExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Tracebird example',
debugShowCheckedModeBanner: false,
navigatorObservers: <NavigatorObserver>[TracebirdNavigatorObserver()],
theme: ThemeData.dark(useMaterial3: true).copyWith(
scaffoldBackgroundColor: const Color(0xFF0B0D11),
),
routes: <String, WidgetBuilder>{
'/': (_) => const CheckoutScreen(),
},
);
}
}
/// A checkout screen whose pay button fails on purpose.
class CheckoutScreen extends StatefulWidget {
/// Creates the checkout screen.
const CheckoutScreen({super.key});
@override
State<CheckoutScreen> createState() => _CheckoutScreenState();
}
const String _promoHtml = '<body style="margin:0;height:100%;display:grid;'
'place-items:center;background:linear-gradient(120deg,#ff6b35,#f7c548 55%,'
'#00d2ff);font:700 20px system-ui;color:#111">FREE DELIVERY (WEBVIEW)</body>';
class _CheckoutScreenState extends State<CheckoutScreen> {
bool _paying = false;
/// A platform view on the screen — the case that breaks naive capture on
/// iOS, and the reason the SDK has a native fallback at all.
late final WebViewController _promo = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..loadHtmlString(_promoHtml);
/// Fails the way a real payment fails: a 500, then a null dereference.
Future<void> _pay() async {
setState(() => _paying = true);
Tracebird.log('[checkout] submitting payment…');
await Future<void>.delayed(const Duration(milliseconds: 600));
Tracebird.recordHttp(
method: 'POST',
url: 'https://api.example.com/api/payments',
status: 500,
milliseconds: 612,
);
Tracebird.log(
'[checkout] payment intent missing from response',
level: LogLevel.error,
);
// The spinner deliberately never stops: that is the bug to report.
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Checkout'),
backgroundColor: const Color(0xFF11141A),
),
body: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
const Text('Order total', style: TextStyle(color: Color(0xFF8B93A1))),
const SizedBox(height: 6),
const Text(
'€84.00',
style: TextStyle(fontSize: 34, fontWeight: FontWeight.w700),
),
const SizedBox(height: 14),
SizedBox(
height: 64,
child: ClipRRect(
borderRadius: BorderRadius.circular(10),
child: WebViewWidget(controller: _promo),
),
),
const SizedBox(height: 14),
// Wrapped so the tester cannot send you someone's card details.
TracebirdMask(
child: Card(
color: const Color(0xFF11141A),
child: ListTile(
leading: const Icon(Icons.credit_card),
title: const Text('4242 4242 4242 4242'),
subtitle: const Text('Sam K. · 04/29'),
),
),
),
const SizedBox(height: 8),
const Text(
'The card is wrapped in TracebirdMask; the banner above it is a '
'WebView, which only a native capture can see on iOS.',
style: TextStyle(color: Color(0xFF8B93A1), fontSize: 11),
),
const Spacer(),
FilledButton(
onPressed: _paying ? null : _pay,
style: FilledButton.styleFrom(
backgroundColor: const Color(0xFF8B5CF6),
minimumSize: const Size.fromHeight(50),
),
child: _paying
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Colors.white,
),
)
: const Text('Pay €84.00'),
),
const SizedBox(height: 12),
const Text(
'Tap Pay, watch it hang, then tap the violet button to report it.',
textAlign: TextAlign.center,
style: TextStyle(color: Color(0xFF8B93A1), fontSize: 12),
),
],
),
),
);
}
}