sentry_flutter 8.10.0-beta.1 sentry_flutter: ^8.10.0-beta.1 copied to clipboard
Sentry SDK for Flutter. This package aims to support different Flutter targets by relying on the many platforms supported by Sentry with native SDKs.
// ignore_for_file: library_private_types_in_public_api
import 'dart:async';
import 'dart:convert';
import 'dart:math';
import 'package:dio/dio.dart';
import 'package:feedback/feedback.dart' as feedback;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:logging/logging.dart';
import 'package:path_provider/path_provider.dart';
import 'package:provider/provider.dart';
import 'package:sentry_dio/sentry_dio.dart';
import 'package:sentry_drift/sentry_drift.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:sentry_hive/sentry_hive.dart';
import 'package:sentry_isar/sentry_isar.dart';
import 'package:sentry_logging/sentry_logging.dart';
import 'package:sentry_sqflite/sentry_sqflite.dart';
import 'package:sqflite/sqflite.dart';
// import 'package:sqflite_common_ffi/sqflite_ffi.dart';
// import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart';
import 'package:universal_platform/universal_platform.dart';
import 'auto_close_screen.dart';
import 'drift/connection/connection.dart';
import 'drift/database.dart';
import 'isar/user.dart';
// ATTENTION: Change the DSN below with your own to see the events in Sentry. Get one at sentry.io
const String exampleDsn =
'https://e85b375ffb9f43cf8bdf9787768149e0@o447951.ingest.sentry.io/5428562';
/// This is an exampleUrl that will be used to demonstrate how http requests are captured.
const String exampleUrl = 'https://jsonplaceholder.typicode.com/todos/';
const _channel = MethodChannel('example.flutter.sentry.io');
var _isIntegrationTest = false;
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
Future<void> main() async {
await setupSentry(
() => runApp(
SentryWidget(
child: DefaultAssetBundle(
bundle: SentryAssetBundle(),
child: const MyApp(),
),
),
),
exampleDsn,
);
}
Future<void> setupSentry(
AppRunner appRunner,
String dsn, {
bool isIntegrationTest = false,
BeforeSendCallback? beforeSendCallback,
}) async {
await SentryFlutter.init(
(options) {
options.dsn = exampleDsn;
options.tracesSampleRate = 1.0;
options.profilesSampleRate = 1.0;
options.reportPackages = false;
options.addInAppInclude('sentry_flutter_example');
options.considerInAppFramesByDefault = false;
options.attachThreads = true;
options.enableWindowMetricBreadcrumbs = true;
options.addIntegration(LoggingIntegration(minEventLevel: Level.INFO));
options.sendDefaultPii = true;
options.reportSilentFlutterErrors = true;
options.attachScreenshot = true;
options.screenshotQuality = SentryScreenshotQuality.low;
options.attachViewHierarchy = true;
// We can enable Sentry debug logging during development. This is likely
// going to log too much for your app, but can be useful when figuring out
// configuration issues, e.g. finding out why your events are not uploaded.
options.debug = true;
options.spotlight = Spotlight(enabled: true);
options.enableTimeToFullDisplayTracing = true;
options.enableMetrics = true;
options.maxRequestBodySize = MaxRequestBodySize.always;
options.maxResponseBodySize = MaxResponseBodySize.always;
options.navigatorKey = navigatorKey;
options.experimental.replay.sessionSampleRate = 1.0;
options.experimental.replay.onErrorSampleRate = 1.0;
_isIntegrationTest = isIntegrationTest;
if (_isIntegrationTest) {
options.dist = '1';
options.environment = 'integration';
options.beforeSend = beforeSendCallback;
}
},
// Init your App.
appRunner: appRunner,
);
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return feedback.BetterFeedback(
child: ChangeNotifierProvider<ThemeProvider>(
create: (_) => ThemeProvider(),
child: Builder(
builder: (context) => MaterialApp(
navigatorKey: navigatorKey,
navigatorObservers: [
SentryNavigatorObserver(),
],
theme: Provider.of<ThemeProvider>(context).theme,
home: const MainScaffold(),
),
),
),
);
}
}
class TooltipButton extends StatelessWidget {
final String text;
final String buttonTitle;
final void Function()? onPressed;
const TooltipButton({
required this.onPressed,
required this.buttonTitle,
required this.text,
super.key,
});
@override
Widget build(BuildContext context) {
return Tooltip(
message: text,
child: ElevatedButton(
onPressed: onPressed,
key: key,
child: Text(buttonTitle),
),
);
}
}
class MainScaffold extends StatelessWidget {
const MainScaffold({
super.key,
});
@override
Widget build(BuildContext context) {
final themeProvider = Provider.of<ThemeProvider>(context);
var icon = Icons.light_mode;
var theme = ThemeData.light();
if (themeProvider.theme.brightness == Brightness.light) {
icon = Icons.dark_mode;
theme = ThemeData.dark();
}
return Scaffold(
appBar: AppBar(
title: const Text('Sentry Flutter Example'),
actions: [
IconButton(
onPressed: () {
themeProvider.theme = theme;
},
icon: Icon(icon),
),
IconButton(
onPressed: () {
themeProvider.updatePrimaryColor(Colors.orange);
},
icon: const Icon(Icons.circle, color: Colors.orange),
),
IconButton(
onPressed: () {
themeProvider.updatePrimaryColor(Colors.green);
},
icon: const Icon(Icons.circle, color: Colors.lime),
),
],
),
body: SingleChildScrollView(
child: Column(
children: [
if (_isIntegrationTest) const IntegrationTestWidget(),
const Center(child: Text('Trigger an action.\n')),
const Padding(
padding: EdgeInsets.all(15), //apply padding to all four sides
child: Center(
child: Text(
'Long press a button to see more information. (hover on web)'),
),
),
TooltipButton(
onPressed: () => navigateToAutoCloseScreen(context),
text:
'Pushes a screen and creates a transaction named \'AutoCloseScreen\' with a child span that finishes after 3 seconds. \nAfter the screen has popped the transaction can then be seen on the performance page.',
buttonTitle: 'Route Navigation Observer',
),
if (!UniversalPlatform.isWeb)
TooltipButton(
onPressed: driftTest,
text:
'Executes CRUD operations on an in-memory with Drift and sends the created transaction to Sentry.',
buttonTitle: 'drift',
),
if (!UniversalPlatform.isWeb)
TooltipButton(
onPressed: hiveTest,
text:
'Executes CRUD operations on an in-memory with Hive and sends the created transaction to Sentry.',
buttonTitle: 'hive',
),
if (!UniversalPlatform.isWeb)
TooltipButton(
onPressed: isarTest,
text:
'Executes CRUD operations on an in-memory with Isart and sends the created transaction to Sentry.',
buttonTitle: 'isar',
),
TooltipButton(
onPressed: sqfliteTest,
text:
'Executes CRUD operations on an in-memory with Hive and sends the created transaction to Sentry.',
buttonTitle: 'sqflite',
),
TooltipButton(
onPressed: () => SecondaryScaffold.openSecondaryScaffold(context),
text:
'Demonstrates how the router integration adds a navigation event to the breadcrumbs that can be seen when throwing an exception for example.',
buttonTitle: 'Open another Scaffold',
),
const TooltipButton(
onPressed: tryCatch,
key: Key('dart_try_catch'),
text: 'Creates a caught exception and sends it to Sentry.',
buttonTitle: 'Dart: try catch',
),
TooltipButton(
onPressed: () => Scaffold.of(context)
.showBottomSheet((context) => const Text('Scaffold error')),
text:
'Creates an uncaught exception and sends it to Sentry. This demonstrates how our flutter error integration catches unhandled exceptions.',
buttonTitle: 'Flutter error : Scaffold.of()',
),
TooltipButton(
// Warning : not captured if a debugger is attached
// https://github.com/flutter/flutter/issues/48972
onPressed: () => throw Exception('Throws onPressed'),
text:
'Creates an uncaught exception and sends it to Sentry. This demonstrates how our flutter error integration catches unhandled exceptions.',
buttonTitle: 'Dart: throw onPressed',
),
TooltipButton(
// Warning : not captured if a debugger is attached
// https://github.com/flutter/flutter/issues/48972
onPressed: () {
assert(false, 'assert failure');
},
text:
'Creates an uncaught exception and sends it to Sentry. This demonstrates how our flutter error integration catches unhandled exceptions.',
buttonTitle: 'Dart: assert',
),
// Calling the SDK with an appRunner will handle errors from Futures
// in SDKs runZonedGuarded onError handler
TooltipButton(
onPressed: () async => asyncThrows(),
text:
'Creates an async uncaught exception and sends it to Sentry. This demonstrates how our flutter error integration catches unhandled exceptions.',
buttonTitle: 'Dart: async throws',
),
TooltipButton(
onPressed: () async => {
await Future.microtask(
() => throw StateError('Failure in a microtask'),
)
},
text:
'Creates an uncaught exception in a microtask and sends it to Sentry. This demonstrates how our flutter error integration catches unhandled exceptions.',
buttonTitle: 'Dart: Fail in microtask',
),
TooltipButton(
onPressed: () async => {
await compute(loop, 10),
},
text:
'Creates an uncaught exception in a compute isolate and sends it to Sentry. This demonstrates how our flutter error integration catches unhandled exceptions.',
buttonTitle: 'Dart: Fail in compute',
),
TooltipButton(
onPressed: () async => {
await Future.delayed(
const Duration(milliseconds: 100),
() => throw StateError('Failure in a Future.delayed'),
),
},
text:
'Creates an uncaught exception in a Future.delayed and sends it to Sentry. This demonstrates how our flutter error integration catches unhandled exceptions.',
buttonTitle: 'Throws in Future.delayed',
),
TooltipButton(
onPressed: () {
// modeled after a real exception
FlutterError.onError?.call(
FlutterErrorDetails(
exception: Exception('A really bad exception'),
silent: false,
context:
DiagnosticsNode.message('while handling a gesture'),
library: 'gesture',
informationCollector: () => [
DiagnosticsNode.message(
'Handler: "onTap" Recognizer: TapGestureRecognizer'),
DiagnosticsNode.message(
'Handler: "onTap" Recognizer: TapGestureRecognizer'),
DiagnosticsNode.message(
'Handler: "onTap" Recognizer: TapGestureRecognizer'),
],
),
);
},
text:
'Creates a FlutterError and passes it to FlutterError.onError callback. This demonstrates how our flutter error integration catches unhandled exceptions.',
buttonTitle: 'Capture from FlutterError.onError',
),
TooltipButton(
onPressed: () {
// Only usable on Flutter >= 3.3
// and needs the following additional setup:
// options.addIntegration(OnErrorIntegration());
(WidgetsBinding.instance.platformDispatcher as dynamic)
.onError
?.call(
Exception('PlatformDispatcher.onError'),
StackTrace.current,
);
},
text:
'This is only usable on Flutter >= 3.3 and requires additional setup: options.addIntegration(OnErrorIntegration());',
buttonTitle: 'Capture from PlatformDispatcher.onError',
),
TooltipButton(
onPressed: () => makeWebRequest(context),
text:
'Attaches web request related spans to the transaction and send it to Sentry.',
buttonTitle: 'Dart: Web request',
),
TooltipButton(
onPressed: () => makeWebRequestWithDio(context),
key: const Key('dio_web_request'),
text:
'Attaches web request related spans to the transaction and send it to Sentry.',
buttonTitle: 'Dio: Web request',
),
TooltipButton(
onPressed: () => showDialogWithTextAndImage(context),
text:
'Attaches asset bundle related spans to the transaction and send it to Sentry.',
buttonTitle: 'Flutter: Load assets',
),
TooltipButton(
onPressed: () {
// ignore: avoid_print
print('A print breadcrumb');
Sentry.captureMessage('A message with a print() Breadcrumb');
},
text:
'Sends a captureMessage to Sentry with a breadcrumb created by a print() statement.',
buttonTitle: 'Record print() as breadcrumb',
),
TooltipButton(
onPressed: () {
Sentry.captureMessage(
'This event has an extra tag',
withScope: (scope) {
scope.setTag('foo', 'bar');
},
);
},
text:
'Sends the capture message event with additional Tag to Sentry.',
buttonTitle: 'Capture message with scope with additional tag',
),
TooltipButton(
onPressed: () async {
final transaction = Sentry.getSpan() ??
Sentry.startTransaction(
'myNewTrWithError3',
'myNewOp',
description: 'myTr myOp',
);
transaction.setTag('myTag', 'myValue');
transaction.setData('myExtra', 'myExtraValue');
await Future.delayed(const Duration(milliseconds: 50));
final span = transaction.startChild(
'childOfMyOp',
description: 'childOfMyOp span',
);
span.setTag('myNewTag', 'myNewValue');
span.setData('myNewData', 'myNewDataValue');
await Future.delayed(const Duration(milliseconds: 70));
await span.finish(status: const SpanStatus.resourceExhausted());
await Future.delayed(const Duration(milliseconds: 90));
final spanChild = span.startChild(
'childOfChildOfMyOp',
description: 'childOfChildOfMyOp span',
);
await Future.delayed(const Duration(milliseconds: 110));
spanChild.startChild(
'unfinishedChild',
description: 'I wont finish',
);
await spanChild.finish(
status: const SpanStatus.internalError());
await Future.delayed(const Duration(milliseconds: 50));
// findPrimeNumber(1000000); // Uncomment to see it with profiling
await transaction.finish(status: const SpanStatus.ok());
},
text:
'Creates a custom transaction, adds child spans and send them to Sentry.',
buttonTitle: 'Capture transaction',
),
TooltipButton(
onPressed: () {
Sentry.captureMessage(
'This message has an attachment',
withScope: (scope) {
const txt = 'Lorem Ipsum dolor sit amet';
scope.addAttachment(
SentryAttachment.fromIntList(
utf8.encode(txt),
'foobar.txt',
contentType: 'text/plain',
),
);
},
);
},
text: 'Sends the capture message with an attachment to Sentry.',
buttonTitle: 'Capture message with attachment',
),
TooltipButton(
onPressed: () {
feedback.BetterFeedback.of(context).show(
(feedback.UserFeedback feedback) {
Sentry.captureMessage(
feedback.text,
withScope: (scope) {
final entries = feedback.extra?.entries;
if (entries != null) {
for (final extra in entries) {
// ignore: deprecated_member_use
scope.setExtra(extra.key, extra.value);
}
}
scope.addAttachment(
SentryAttachment.fromUint8List(
feedback.screenshot,
'feedback.png',
contentType: 'image/png',
),
);
},
);
},
);
},
text:
'Sends the capture message with an image attachment to Sentry.',
buttonTitle: 'Capture message with image attachment',
),
TooltipButton(
onPressed: () async {
final id = await Sentry.captureMessage('UserFeedback');
if (!context.mounted) return;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
SentryFeedbackWidget(associatedEventId: id),
fullscreenDialog: true,
),
);
},
text:
'Shows a custom feedback dialog without an ongoing event that captures and sends user feedback data to Sentry.',
buttonTitle: 'Capture Feedback',
),
TooltipButton(
onPressed: () {
final log = Logger('Logging');
log.info('My Logging test');
},
text:
'Demonstrates the logging integration. log.info() will create an info event send it to Sentry.',
buttonTitle: 'Logging',
),
TooltipButton(
onPressed: () async {
final span = Sentry.getSpan() ??
Sentry.startTransaction(
'testMetrics', 'span summary example',
bindToScope: true);
// ignore: deprecated_member_use
Sentry.metrics().increment('increment key',
unit: DurationSentryMeasurementUnit.day);
// ignore: deprecated_member_use
Sentry.metrics().distribution('distribution key',
value: Random().nextDouble() * 10);
// ignore: deprecated_member_use
Sentry.metrics().set('set int key',
value: Random().nextInt(100),
tags: {'myTag': 'myValue', 'myTag2': 'myValue2'});
// ignore: deprecated_member_use
Sentry.metrics().set('set string key',
stringValue: 'Random n ${Random().nextInt(100)}');
// ignore: deprecated_member_use
Sentry.metrics()
.gauge('gauge key', value: Random().nextDouble() * 10);
// ignore: deprecated_member_use
Sentry.metrics().timing(
'timing key',
function: () async => await Future.delayed(
Duration(milliseconds: Random().nextInt(100)),
() => span.finish()),
unit: DurationSentryMeasurementUnit.milliSecond,
);
},
text:
'Demonstrates the metrics. It creates several metrics and send them to Sentry.',
buttonTitle: 'Metrics',
),
if (UniversalPlatform.isIOS || UniversalPlatform.isMacOS)
const CocoaExample(),
if (UniversalPlatform.isAndroid) const AndroidExample(),
// ignore: invalid_use_of_internal_member
if (SentryFlutter.native != null)
ElevatedButton(
onPressed: () async {
SentryFlutter.nativeCrash();
},
child: const Text('Sentry.nativeCrash'),
),
].map((widget) {
if (kIsWeb) {
// Add vertical padding to web so the tooltip doesn't obstruct the clicking of the button below.
return Padding(
padding: const EdgeInsets.only(top: 18.0, bottom: 18.0),
child: widget,
);
}
return widget;
}).toList(),
),
),
);
}
Future<void> isarTest() async {
final tr = Sentry.startTransaction(
'isarTest',
'db',
bindToScope: true,
);
final dir = await getApplicationDocumentsDirectory();
final isar = await SentryIsar.open(
[UserSchema],
directory: dir.path,
);
final newUser = User()
..name = 'Joe Dirt'
..age = 36;
await isar.writeTxn(() async {
await isar.users.put(newUser); // insert & update
});
final existingUser = await isar.users.get(newUser.id); // get
await isar.writeTxn(() async {
await isar.users.delete(existingUser!.id); // delete
});
await tr.finish(status: const SpanStatus.ok());
}
Future<void> hiveTest() async {
final tr = Sentry.startTransaction(
'hiveTest',
'db',
bindToScope: true,
);
final appDir = await getApplicationDocumentsDirectory();
SentryHive.init(appDir.path);
final catsBox = await SentryHive.openBox<Map>('cats');
await catsBox.put('fluffy', {'name': 'Fluffy', 'age': 4});
await catsBox.put('loki', {'name': 'Loki', 'age': 2});
await catsBox.clear();
await catsBox.close();
SentryHive.close();
await tr.finish(status: const SpanStatus.ok());
}
Future<void> sqfliteTest() async {
final tr = Sentry.startTransaction(
'sqfliteTest',
'db',
bindToScope: true,
);
// databaseFactory = databaseFactoryFfiWeb; // or databaseFactoryFfi // or SentrySqfliteDatabaseFactory()
// final sqfDb = await openDatabase(inMemoryDatabasePath);
final db = await openDatabaseWithSentry(inMemoryDatabasePath);
// final db = SentryDatabase(sqfDb);
// final batch = db.batch();
await db.execute('''
CREATE TABLE Product (
id INTEGER PRIMARY KEY,
title TEXT
)
''');
final dbTitles = <String>[];
for (int i = 1; i <= 20; i++) {
final title = 'Product $i';
dbTitles.add(title);
await db.insert('Product', <String, Object?>{'title': title});
}
await db.query('Product');
await db.transaction((txn) async {
await txn
.insert('Product', <String, Object?>{'title': 'Product Another one'});
await txn.delete('Product',
where: 'title = ?', whereArgs: ['Product Another one']);
});
await db.delete('Product', where: 'title = ?', whereArgs: ['Product 1']);
// final batch = db.batch();
// batch.delete('Product', where: 'title = ?', whereArgs: dbTitles);
// await batch.commit();
await db.close();
await tr.finish(status: const SpanStatus.ok());
}
Future<void> driftTest() async {
final tr = Sentry.startTransaction(
'driftTest',
'db',
bindToScope: true,
);
final executor = SentryQueryExecutor(
() async => inMemoryExecutor(),
databaseName: 'sentry_in_memory_db',
);
final db = AppDatabase(executor);
await db.into(db.todoItems).insert(
TodoItemsCompanion.insert(
title: 'This is a test thing',
content: 'test',
),
);
await db.select(db.todoItems).get();
await db.close();
await tr.finish(status: const SpanStatus.ok());
}
}
extension BuildContextExtension on BuildContext {
bool get isMounted {
try {
return (this as dynamic).mounted;
} on NoSuchMethodError catch (_) {
// ignore, only available in newer Flutter versions
}
return true;
}
}
class AndroidExample extends StatelessWidget {
const AndroidExample({super.key});
@override
Widget build(BuildContext context) {
return Column(children: [
ElevatedButton(
onPressed: () async {
await execute('throw');
},
child: const Text('Kotlin Throw unhandled exception'),
),
ElevatedButton(
onPressed: () async {
await execute('capture');
},
child: const Text('Kotlin Capture Exception'),
),
ElevatedButton(
// ANR is disabled by default, enable it to test it
onPressed: () async {
await execute('anr');
},
child: const Text('ANR: UI blocked 6 seconds'),
),
ElevatedButton(
onPressed: () async {
await execute('cpp_capture_message');
},
child: const Text('C++ Capture message'),
),
ElevatedButton(
onPressed: () async {
await execute('crash');
},
child: const Text('C++ SEGFAULT'),
),
ElevatedButton(
onPressed: () async {
await execute('platform_exception');
},
child: const Text('Platform exception'),
),
]);
}
}
void navigateToAutoCloseScreen(BuildContext context) {
Navigator.push(
context,
MaterialPageRoute(
settings: const RouteSettings(name: 'AutoCloseScreen'),
builder: (context) => SentryDisplayWidget(child: const AutoCloseScreen()),
),
);
}
Future<void> tryCatch() async {
try {
throw StateError('try catch');
} catch (error, stackTrace) {
await Sentry.captureException(error, stackTrace: stackTrace);
}
}
Future<void> asyncThrows() async {
throw StateError('async throws');
}
class IntegrationTestWidget extends StatefulWidget {
const IntegrationTestWidget({super.key});
@override
State<StatefulWidget> createState() {
return _IntegrationTestWidgetState();
}
}
class _IntegrationTestWidgetState extends State<IntegrationTestWidget> {
_IntegrationTestWidgetState();
var _output = "--";
var _isLoading = false;
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(
_output,
key: const Key('output'),
),
_isLoading
? const CircularProgressIndicator()
: ElevatedButton(
onPressed: () async => await _captureException(),
child: const Text('captureException'),
)
],
);
}
Future<void> _captureException() async {
setState(() {
_isLoading = true;
});
try {
throw Exception('captureException');
} catch (error, stackTrace) {
final id = await Sentry.captureException(error, stackTrace: stackTrace);
setState(() {
_output = id.toString();
_isLoading = false;
});
}
}
}
class CocoaExample extends StatelessWidget {
const CocoaExample({super.key});
@override
Widget build(BuildContext context) {
return Column(
children: [
ElevatedButton(
onPressed: () async {
await execute('fatalError');
},
child: const Text('Swift fatalError'),
),
ElevatedButton(
onPressed: () async {
await execute('capture');
},
child: const Text('Swift Capture NSException'),
),
ElevatedButton(
onPressed: () async {
await execute('capture_message');
},
child: const Text('Swift Capture message'),
),
ElevatedButton(
onPressed: () async {
await execute('throw');
},
child: const Text('Objective-C Throw unhandled exception'),
),
ElevatedButton(
onPressed: () async {
await execute('crash');
},
child: const Text('Objective-C SEGFAULT'),
),
],
);
}
}
/// compute can only take a top-level function, but not instance or static methods.
// Top-level functions are functions declared not inside a class and not inside another function
int loop(int val) {
var count = 0;
for (var i = 1; i <= val; i++) {
count += i;
}
throw StateError('from a compute isolate $count');
}
class SecondaryScaffold extends StatelessWidget {
const SecondaryScaffold({super.key});
static Future<void> openSecondaryScaffold(BuildContext context) {
return Navigator.push(
context,
MaterialPageRoute<void>(
settings:
const RouteSettings(name: 'SecondaryScaffold', arguments: 'foobar'),
builder: (context) {
return const SecondaryScaffold();
},
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('SecondaryScaffold'),
),
body: Center(
child: Column(
children: [
const Text(
'You have added a navigation event '
'to the crash reports breadcrumbs.',
),
MaterialButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('Go back'),
),
MaterialButton(
onPressed: () {
throw Exception('Exception from SecondaryScaffold');
},
child: const Text('throw uncaught exception'),
),
],
),
),
);
}
}
Future<void> makeWebRequest(BuildContext context) async {
final transaction = Sentry.getSpan() ??
Sentry.startTransaction(
'flutterwebrequest',
'request',
bindToScope: true,
);
final client = SentryHttpClient(
failedRequestStatusCodes: [SentryStatusCode.range(400, 500)],
);
// We don't do any exception handling here.
// In case of an exception, let it get caught and reported to Sentry
final response = await client.get(Uri.parse(exampleUrl));
await transaction.finish(status: const SpanStatus.ok());
if (!context.mounted) return;
await showDialog<void>(
context: context,
builder: (context) {
return AlertDialog(
title: Text('Response ${response.statusCode}'),
content: SingleChildScrollView(
child: Text(response.body),
),
actions: [
MaterialButton(
onPressed: () => Navigator.pop(context),
child: const Text('Close'),
)
],
);
},
);
}
Future<void> makeWebRequestWithDio(BuildContext context) async {
final dio = Dio();
dio.addSentry();
final transaction = Sentry.getSpan() ??
Sentry.startTransaction(
'dio-web-request',
'request',
bindToScope: true,
);
final span = transaction.startChild(
'dio',
description: 'desc',
);
Response<String>? response;
try {
response = await dio.get<String>(exampleUrl);
span.status = const SpanStatus.ok();
} catch (exception, stackTrace) {
span.throwable = exception;
span.status = const SpanStatus.internalError();
await Sentry.captureException(exception, stackTrace: stackTrace);
} finally {
await span.finish();
}
if (!context.mounted) return;
await showDialog<void>(
context: context,
builder: (context) {
return AlertDialog(
title: Text('Response ${response?.statusCode}'),
content: SingleChildScrollView(
child: Text(response?.data ?? 'failed request'),
),
actions: [
MaterialButton(
onPressed: () => Navigator.pop(context),
child: const Text('Close'),
)
],
);
},
);
}
Future<void> showDialogWithTextAndImage(BuildContext context) async {
final transaction = Sentry.getSpan() ??
Sentry.startTransaction(
'asset-bundle-transaction',
'load',
bindToScope: true,
);
final text =
await DefaultAssetBundle.of(context).loadString('assets/lorem-ipsum.txt');
if (!context.mounted) return;
final imageBytes =
await DefaultAssetBundle.of(context).load('assets/sentry-wordmark.png');
await showDialog<void>(
// ignore: use_build_context_synchronously
context: context,
// gets tracked if using SentryNavigatorObserver
routeSettings: const RouteSettings(
name: 'AssetBundle dialog',
),
builder: (context) {
return AlertDialog(
title: const Text('Asset Example'),
content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Use various ways an image is included in the app.
// Local asset images are not obscured in replay recording.
Image.asset('assets/sentry-wordmark.png'),
Image.asset('assets/sentry-wordmark.png', bundle: rootBundle),
Image.asset('assets/sentry-wordmark.png',
bundle: DefaultAssetBundle.of(context)),
Image.network(
'https://www.gstatic.com/recaptcha/api2/logo_48.png'),
Image.memory(imageBytes.buffer.asUint8List()),
Text(text),
],
),
),
actions: [
MaterialButton(
onPressed: () => Navigator.pop(context),
child: const Text('Close'),
)
],
);
},
);
await transaction.finish(status: const SpanStatus.ok());
}
class ThemeProvider extends ChangeNotifier {
ThemeData _theme = ThemeData.light();
ThemeData get theme => _theme;
set theme(ThemeData theme) {
_theme = theme;
notifyListeners();
}
void updatePrimaryColor(MaterialColor color) {
if (theme.brightness == Brightness.light) {
theme = ThemeData(primarySwatch: color, brightness: theme.brightness);
} else {
theme = ThemeData(primarySwatch: color, brightness: theme.brightness);
}
}
}
Future<void> execute(String method) async {
await _channel.invokeMethod(method);
}
// Don't inline this one or it shows up as an anonymous closure in profiles.
@pragma("vm:never-inline")
int findPrimeNumber(int n) {
int count = 0;
int a = 2;
while (count < n) {
int b = 2;
bool prime = true; // to check if found a prime
while (b * b <= a) {
if (a % b == 0) {
prime = false;
break;
}
b++;
}
if (prime) {
count++;
}
a++;
}
return a - 1;
}