failures 0.0.9 copy "failures: ^0.0.9" to clipboard
failures: ^0.0.9 copied to clipboard

Gracefully handle failures in your Dart/Flutter application

example/lib/main.dart

import 'dart:ui';

import 'package:dio/dio.dart';
import 'package:failures/failures.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:multi_logger/multi_logger.dart';
import 'package:sentry_flutter/sentry_flutter.dart' hide SentryLogger;
// ignore: implementation_imports
import 'package:sentry_flutter/src/integrations/flutter_error_integration.dart';

import 'i18n/translations.g.dart';
import 'failures/location_failures.dart';

final dio = Dio();
final failureNotifier = ValueNotifier<Failure?>(null);

void main() async {
  initLogging();
  initFailures();

  SentryWidgetsFlutterBinding.ensureInitialized();

  await LocaleSettings.setLocaleRaw('en');

  await SentryFlutter.init(
    (options) {
      options.dsn = const String.fromEnvironment('SENTRY_DSN');
      options.debug = false;
      options.enableAppHangTracking = !kDebugMode;

      // Remove default Sentry integrations for handling platform
      // and Flutter errors. We'll handle them on our own, but still
      // will track them to Sentry, just with more information added
      final onPlatformError = options
        .integrations
        .whereType<OnErrorIntegration>()
        .firstOrNull;

      final onFlutterError = options
        .integrations
        .whereType<FlutterErrorIntegration>()
        .firstOrNull;

      if (onPlatformError != null) {
        options.removeIntegration(onPlatformError);
      }
      if (onFlutterError != null) {
        options.removeIntegration(onFlutterError);
      }
    },

    appRunner: () {
      return runApp(MyApp());
    },
  );
}

void initLogging() {
  logger = MultiLogger(
    beforeLog: (event) {
      // If log called with [Failure] as a message let's make
      // the failure itself an error of [LogEvent] and use the
      // failure description as a message instead
      if (event.message is Failure) {
        final Failure failure = event.message;

        return event.copyWith(
          message: failure.message,
          error: failure,
          stackTrace: failure.stackTrace,
          extra: failure.extra,
        );
      }
      return event;
    },
    loggers: [
      ConsoleLogger(
        level: LogLevel.trace,
        excludePaths: [
          'multi_logger',
          'dio',
          'sentry',
          'flutter',
        ],
      ),
      SentryLogger(
        level: LogLevel.debug,
      ),
    ]
  );
}

void initFailures() {
  failures.register<DioFailure, DioException>(
    create: DioFailure.new,
  );

  failures.register<LocationFailure, LocationError>(
    create: LocationFailure.new,
    descriptor: LocationFailureDescriptor(),
  );

  failures.onFailure = (failure) {
    if (failure.isException) {
      logger.error(failure);
    } else {
      logger.warning(failure);
    }
    failureNotifier.value = failure;
  };

  // Here we catch Flutter errors
  // (ref: https://docs.flutter.dev/testing/errors)
  FlutterError.onError = (details) {
    failures.handle(
      details.exception,
      details.stack,
    );
  };

  // Here we catch Platform errors
  // (ref: https://docs.flutter.dev/testing/errors)
  PlatformDispatcher.instance.onError = (error, stackTrace) {
    failures.handle(error, stackTrace);
    return true;
  };
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Builder(
        builder: (context) => Scaffold(
          body: SafeArea(
            bottom: false,
            child: page(context),
          ),
          floatingActionButton: FloatingActionButton.small(
            onPressed: () async {
              await LocaleSettings.setLocaleRaw(
                LocaleSettings.currentLocale.languageCode == 'en' ? 'el' : 'en',
              );
              setState(() => {});
            },
            child: Text(LocaleSettings.currentLocale.languageCode.toUpperCase()),
          ),
        ),
      ),
    );
  }

  Widget page(BuildContext context) {
    return  ValueListenableBuilder<Failure?>(
      valueListenable: failureNotifier,
      builder: (_, failure, __) => Container(
        padding: EdgeInsetsGeometry.only(top: 12.0, left: 20.0, right: 20.0),
        alignment: Alignment.center,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.start,
          crossAxisAlignment: CrossAxisAlignment.center,
          spacing: 12.0,
          children: [
            Text(
              'Throw exception of type:',
              style: TextStyle().copyWith(fontWeight: FontWeight.bold),
            ),
            Column(
              children: [
                Wrap(
                  spacing: 8.0,
                  runSpacing: 8.0,
                  alignment: WrapAlignment.center,
                  children: [
                    _button(
                      context,
                      onPressed: () {
                        throw 'This is an instance of String';
                      },
                      title: 'String',
                    ),
                    _button(
                      context,
                      onPressed: () {
                        throw Exception('This is an instance of Exception');
                      },
                      title: 'Exception',
                    ),
                    _button(
                      context,
                      onPressed: () {
                        dio.get(
                          'https://www.production.stg.douleutaras.gr/api',
                          queryParameters: {
                            'param1': 'value1',
                          },
                        );
                      },
                      title: 'DioException',
                    ),
                    _button(
                      context,
                      onPressed: () {
                        [1, 2][3];
                      },
                      title: 'RangeError',
                    ),
                    _button(
                      context,
                      onPressed: () {
                        throw LocationFailure.placeNotFound(
                          message: 'Place with specified ID was not found',
                          extra: {
                            'placeId': '123',
                          },
                        );
                      },
                      title: 'LocationFailure',
                    ),
                  ],
                )
              ],
            ),
            if (failure != null) ...[
              Expanded(
                child: DefaultTabController(
                  length: 3,
                  initialIndex: 0,
                  child: Scaffold(
                    appBar: AppBar(
                      toolbarHeight: 0.0,
                      bottom: const TabBar(
                        tabs: [
                          Tab(text: 'General'),
                          Tab(text: 'Stack'),
                          Tab(text: 'Extra'),
                        ],
                      ),
                    ),
                    body: TabBarView(
                      children: [
                        _descriptor(context, failure),
                        _stackTrace(context, failure),
                        _extra(context, failure),
                      ],
                    ),
                  ),
                ),
              ),
            ],
          ],
        ),
      ),
    );
  }

  Widget _descriptor(
    BuildContext context,
    Failure failure,
  ) {
    return SingleChildScrollView(
      padding: EdgeInsets.only(top: 10.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        spacing: 8.0,
        children: [
          _keyValue(
            context,
            key: 'summary',
            value: failure.summary,
          ),
          if (failure.message != null) ...[
            _keyValue(
              context,
              key: 'message',
              value: failure.message!,
            ),
          ],
          if (failure.title != null) ...[
            _keyValue(
              context,
              key: 'title',
              value: failure.title!,
            ),
          ],
          if (failure.description != null) ...[
            _keyValue(
              context,
              key: 'description',
              value: failure.description!,
            ),
          ],
        ],
      ),
    );
  }

  Widget _stackTrace(
    BuildContext context,
    Failure failure,
  ) {
    return SingleChildScrollView(
      padding: EdgeInsets.only(top: 10.0),
      child: Text(failure.stackTrace.original.toString())
    );
  }

  Widget _extra(
    BuildContext context,
    Failure failure,
  ) {
    if (failure.extra != null) {
      return SingleChildScrollView(
        padding: EdgeInsets.only(top: 10.0, bottom: 20.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          spacing: 8.0,
          children: failure.extra!.entries.map((entry) {
            return _keyValue(
              context,
              key: entry.key.toString(),
              value: entry.value.toString().trim(),
            );
          }).toList(),
        )
      );
    }

    return const SizedBox.shrink();
  }

  Widget _keyValue(
    BuildContext context, {
    required String key,
    required String value,
  }) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(
          key.toString(),
          style: TextStyle().copyWith(fontWeight: FontWeight.bold),
        ),
        Text(value.toString()),
      ],
    );
  }

  Widget _button(
    BuildContext context, {
    required String title,
    required VoidCallback? onPressed,
  }) {
    return SizedBox(
      height: 36.0,
      child: FilledButton(
        onPressed: onPressed,
        child: Text(title),
      ),
    );
  }
}
0
likes
160
points
336
downloads

Publisher

unverified uploader

Weekly Downloads

Gracefully handle failures in your Dart/Flutter application

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

dio, equatable, meta, stack_trace

More

Packages that depend on failures