From 1ace0cbc35937348188280b74e2a310160669a38 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 26 Jul 2026 09:34:47 +0000 Subject: [PATCH 1/2] Add an example app pub.dev docks 10 points for a missing example. Add a minimal Flutter app under example/ demonstrating the listener setup, print strategies, custom data and logging from a cubit-like class. Also analyze the example on CI and bump the version so the example ships with the next release. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01XYcNPtYk6ZrQEu9uGHqjq2 --- .github/workflows/test.yml | 4 + CHANGELOG.md | 4 + README.md | 3 + example/analysis_options.yaml | 1 + example/lib/main.dart | 146 ++++++++++++++++++++++++++++++++++ example/pubspec.yaml | 20 +++++ pubspec.yaml | 2 +- 7 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 example/analysis_options.yaml create mode 100644 example/lib/main.dart create mode 100644 example/pubspec.yaml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4d47d06..5944b0a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -40,6 +40,10 @@ jobs: - name: Download pub dependencies run: flutter pub get + - name: Download example pub dependencies + run: flutter pub get + working-directory: example + - name: Run analyzer run: flutter analyze diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c33c75..2ec28c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 6.0.1 + +- Add an example app. + # 6.0.0 - Bump flutter_bugfender version to 5.0.0 diff --git a/README.md b/README.md index 88136cf..cf771fe 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ A library helping integrate Bugfender with the [logging] package. +See the [example app][example] for a runnable version of the snippets below. + ## Usage ### Setup @@ -108,6 +110,7 @@ We are **top-tier experts** focused on Flutter Enterprise solutions. [build-badge]: https://img.shields.io/github/actions/workflow/status/leancodepl/logging_bugfender/test.yml?branch=master [build-badge-link]: https://github.com/leancodepl/logging_bugfender/actions/workflows/test.yml [logging]: https://pub.dev/packages/logging +[example]: https://github.com/leancodepl/logging_bugfender/tree/master/example [banner-img]: https://raw.githubusercontent.com/leancodepl/logging_bugfender/refs/heads/master/docs/imgs/banner.png [leancode-landing]: https://leancode.co/?utm_source=github.com&utm_medium=referral&utm_campaign=logging-bugfender [leancode-estimate]: https://leancode.co/get-estimate?utm_source=github.com&utm_medium=referral&utm_campaign=logging-bugfender diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml new file mode 100644 index 0000000..4af9cbc --- /dev/null +++ b/example/analysis_options.yaml @@ -0,0 +1 @@ +include: package:leancode_lint/analysis_options.yaml diff --git a/example/lib/main.dart b/example/lib/main.dart new file mode 100644 index 0000000..364b1b4 --- /dev/null +++ b/example/lib/main.dart @@ -0,0 +1,146 @@ +// An example app showing how to wire `logging_bugfender` into a Flutter app. +// +// This directory ships without the platform folders – run `flutter create .` +// in it once before `flutter run`. + +import 'package:flutter/material.dart'; +import 'package:logging/logging.dart'; +import 'package:logging_bugfender/logging_bugfender.dart'; + +/// Your secret app key, taken from the Bugfender dashboard. +/// +/// Run the example with your own key: +/// `flutter run --dart-define=BUGFENDER_APP_KEY=`. +const bugfenderAppKey = String.fromEnvironment('BUGFENDER_APP_KEY'); + +/// Flip this to `false` to see how the setup behaves on production. +const debugMode = true; + +/// The key under which the signed in user is reported to Bugfender. +const usernameKey = 'username'; + +/// The listener has to outlive the logger it listens to, so it's kept in a +/// top-level variable. In a real app it would usually live in your DI +/// container. +late final LoggingBugfenderListener loggingListener; + +void main() { + loggingListener = setupLogger(debugMode: debugMode); + + runApp(const ExampleApp()); +} + +/// Creates a [LoggingBugfenderListener] and attaches it to the root logger, so +/// that every record logged anywhere in the app ends up in Bugfender. +LoggingBugfenderListener setupLogger({required bool debugMode}) { + final LoggingBugfenderListener listener; + + if (debugMode) { + // During debugging, you'll usually want to log everything and to also see + // the logs in the console. + Logger.root.level = Level.ALL; + listener = LoggingBugfenderListener( + bugfenderAppKey, + consolePrintStrategy: const PlainTextPrintStrategy(), + ); + } else { + // On production, you probably want to log only INFO and above. + Logger.root.level = Level.INFO; + listener = LoggingBugfenderListener(bugfenderAppKey); + } + + listener.listen(Logger.root); + + return listener; +} + +class ExampleApp extends StatelessWidget { + const ExampleApp({super.key}); + + @override + Widget build(BuildContext context) { + return const MaterialApp( + title: 'logging_bugfender example', + home: ExamplePage(), + ); + } +} + +class ExamplePage extends StatefulWidget { + const ExamplePage({super.key}); + + @override + State createState() => _ExamplePageState(); +} + +class _ExamplePageState extends State { + final _cubit = FooBarCubit(); + + bool _signedIn = false; + + /// Custom data is attached to every log sent from this device, which makes + /// it easy to tell whose session you're looking at in the Bugfender console. + Future _toggleSignIn() async { + if (_signedIn) { + // After the user signs out. + await loggingListener.removeCustomData(usernameKey); + } else { + // After the user signs in. + await loggingListener.setCustomData(usernameKey, 'jane.doe'); + } + + if (mounted) { + setState(() => _signedIn = !_signedIn); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('logging_bugfender')), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + FilledButton( + onPressed: _cubit.doSomething, + child: const Text('Do something'), + ), + FilledButton( + onPressed: _cubit.doSomethingFailing, + child: const Text('Do something that fails'), + ), + const SizedBox(height: 16), + FilledButton( + onPressed: _toggleSignIn, + child: Text(_signedIn ? 'Sign out' : 'Sign in'), + ), + ], + ), + ), + ); + } +} + +/// A stand-in for a piece of business logic that reports what it's doing. +/// +/// Every class that logs should have its own [Logger] – its name is included +/// in the log message, so you always know where a record came from. +class FooBarCubit { + final _logger = Logger('FooBarCubit'); + + void doSomething() { + _logger.info('Successfully did something'); + } + + void doSomethingFailing() { + _logger.fine('About to do something else'); + + try { + throw const FormatException('Malformed response'); + } catch (err, st) { + // Both the error and the stack trace are sent to Bugfender. + _logger.severe('Failed doing something else', err, st); + } + } +} diff --git a/example/pubspec.yaml b/example/pubspec.yaml new file mode 100644 index 0000000..1144152 --- /dev/null +++ b/example/pubspec.yaml @@ -0,0 +1,20 @@ +name: logging_bugfender_example +description: An example app showing how to integrate Bugfender with the logging package. +publish_to: none + +environment: + sdk: '>=3.3.0 <4.0.0' + flutter: '>=3.19.0' + +dependencies: + flutter: + sdk: flutter + logging: ^1.0.2 + logging_bugfender: + path: ../ + +dev_dependencies: + leancode_lint: '>=2.1.0 <2.2.0' + +flutter: + uses-material-design: true diff --git a/pubspec.yaml b/pubspec.yaml index edcfafc..31e88ce 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: logging_bugfender description: A library helping integrate Bugfender with the logging package. -version: 6.0.0 +version: 6.0.1 homepage: https://github.com/leancodepl/logging_bugfender environment: From 349995014a424dc9da8097bdaf4346bd2f2779ed Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 26 Jul 2026 09:39:41 +0000 Subject: [PATCH 2/2] Simplify the example Drop the dart-define app key, the fabricated cubit and its thrown error, and the top-level listener. The listener is now passed down through the widget tree and the only error handling left wraps a real call. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01XYcNPtYk6ZrQEu9uGHqjq2 --- example/lib/main.dart | 122 +++++++++++++++--------------------------- 1 file changed, 42 insertions(+), 80 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 364b1b4..c9a1b77 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -7,90 +7,75 @@ import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; import 'package:logging_bugfender/logging_bugfender.dart'; -/// Your secret app key, taken from the Bugfender dashboard. -/// -/// Run the example with your own key: -/// `flutter run --dart-define=BUGFENDER_APP_KEY=`. -const bugfenderAppKey = String.fromEnvironment('BUGFENDER_APP_KEY'); - -/// Flip this to `false` to see how the setup behaves on production. -const debugMode = true; - /// The key under which the signed in user is reported to Bugfender. const usernameKey = 'username'; -/// The listener has to outlive the logger it listens to, so it's kept in a -/// top-level variable. In a real app it would usually live in your DI -/// container. -late final LoggingBugfenderListener loggingListener; - void main() { - loggingListener = setupLogger(debugMode: debugMode); - - runApp(const ExampleApp()); -} - -/// Creates a [LoggingBugfenderListener] and attaches it to the root logger, so -/// that every record logged anywhere in the app ends up in Bugfender. -LoggingBugfenderListener setupLogger({required bool debugMode}) { - final LoggingBugfenderListener listener; - - if (debugMode) { - // During debugging, you'll usually want to log everything and to also see - // the logs in the console. - Logger.root.level = Level.ALL; - listener = LoggingBugfenderListener( - bugfenderAppKey, - consolePrintStrategy: const PlainTextPrintStrategy(), - ); - } else { - // On production, you probably want to log only INFO and above. - Logger.root.level = Level.INFO; - listener = LoggingBugfenderListener(bugfenderAppKey); - } - - listener.listen(Logger.root); - - return listener; + // During debugging, you'll usually want to log everything. On production, + // `Level.INFO` and above is usually enough. + Logger.root.level = Level.ALL; + + final loggingListener = LoggingBugfenderListener( + 'my-very-secret-app-key', + // Logs are only sent to Bugfender by default – printing them to the + // console too is handy while debugging. + consolePrintStrategy: const PlainTextPrintStrategy(), + )..listen(Logger.root); + + runApp(ExampleApp(loggingListener: loggingListener)); } class ExampleApp extends StatelessWidget { - const ExampleApp({super.key}); + const ExampleApp({super.key, required this.loggingListener}); + + final LoggingBugfenderListener loggingListener; @override Widget build(BuildContext context) { - return const MaterialApp( + return MaterialApp( title: 'logging_bugfender example', - home: ExamplePage(), + home: ExamplePage(loggingListener: loggingListener), ); } } class ExamplePage extends StatefulWidget { - const ExamplePage({super.key}); + const ExamplePage({super.key, required this.loggingListener}); + + final LoggingBugfenderListener loggingListener; @override State createState() => _ExamplePageState(); } class _ExamplePageState extends State { - final _cubit = FooBarCubit(); + /// Every class that logs should have its own [Logger] – its name is included + /// in the log message, so you always know where a record came from. + final _logger = Logger('ExamplePage'); bool _signedIn = false; /// Custom data is attached to every log sent from this device, which makes /// it easy to tell whose session you're looking at in the Bugfender console. Future _toggleSignIn() async { - if (_signedIn) { - // After the user signs out. - await loggingListener.removeCustomData(usernameKey); - } else { - // After the user signs in. - await loggingListener.setCustomData(usernameKey, 'jane.doe'); + final signedIn = _signedIn; + + try { + if (signedIn) { + await widget.loggingListener.removeCustomData(usernameKey); + } else { + await widget.loggingListener.setCustomData(usernameKey, 'jane.doe'); + } + } catch (err, st) { + // Both the error and the stack trace are sent to Bugfender. + _logger.severe('Failed updating the custom data', err, st); + return; } + _logger.info(signedIn ? 'Signed out' : 'Signed in'); + if (mounted) { - setState(() => _signedIn = !_signedIn); + setState(() => _signedIn = !signedIn); } } @@ -103,12 +88,12 @@ class _ExamplePageState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ FilledButton( - onPressed: _cubit.doSomething, - child: const Text('Do something'), + onPressed: () => _logger.fine('The details button was tapped'), + child: const Text('Log at FINE'), ), FilledButton( - onPressed: _cubit.doSomethingFailing, - child: const Text('Do something that fails'), + onPressed: () => _logger.warning('The cache is almost full'), + child: const Text('Log at WARNING'), ), const SizedBox(height: 16), FilledButton( @@ -121,26 +106,3 @@ class _ExamplePageState extends State { ); } } - -/// A stand-in for a piece of business logic that reports what it's doing. -/// -/// Every class that logs should have its own [Logger] – its name is included -/// in the log message, so you always know where a record came from. -class FooBarCubit { - final _logger = Logger('FooBarCubit'); - - void doSomething() { - _logger.info('Successfully did something'); - } - - void doSomethingFailing() { - _logger.fine('About to do something else'); - - try { - throw const FormatException('Malformed response'); - } catch (err, st) { - // Both the error and the stack trace are sent to Bugfender. - _logger.severe('Failed doing something else', err, st); - } - } -}