diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index 8ce9bcb..49e6d7f 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -40,16 +40,16 @@ jobs: - name: mono_repo self validate run: dart pub global run mono_repo generate --validate job_002: - name: "analyze; PKGS: bro_abstract_logger, bro_abstract_manager, bro_config_manager, bro_file_utility, bro_global_manager, bro_list_utility, bro_logger_manager, bro_types_utility, bro_yaml_utility; `flutter analyze --fatal-infos .`, `dart pub global activate pana && pana --line-length 100 .`" + name: "analyze; PKGS: bro_abstract_logger, bro_abstract_manager, bro_config_manager, bro_file_utility, bro_firebase_core, bro_firebase_crashlytics, bro_global_manager, bro_list_utility, bro_logger_manager, bro_platform_utility, bro_types_utility, bro_yaml_utility; `flutter analyze --fatal-infos .`, `dart pub global activate pana && pana --line-length 100 .`" runs-on: ubuntu-latest steps: - name: Cache Pub hosted dependencies uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 with: path: "~/.pub-cache/hosted" - key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:bro_abstract_logger-bro_abstract_manager-bro_config_manager-bro_file_utility-bro_global_manager-bro_list_utility-bro_logger_manager-bro_types_utility-bro_yaml_utility;commands:analyze-command" + key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:bro_abstract_logger-bro_abstract_manager-bro_config_manager-bro_file_utility-bro_firebase_core-bro_firebase_crashlytics-bro_global_manager-bro_list_utility-bro_logger_manager-bro_platform_utility-bro_types_utility-bro_yaml_utility;commands:analyze-command" restore-keys: | - os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:bro_abstract_logger-bro_abstract_manager-bro_config_manager-bro_file_utility-bro_global_manager-bro_list_utility-bro_logger_manager-bro_types_utility-bro_yaml_utility + os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:bro_abstract_logger-bro_abstract_manager-bro_config_manager-bro_file_utility-bro_firebase_core-bro_firebase_crashlytics-bro_global_manager-bro_list_utility-bro_logger_manager-bro_platform_utility-bro_types_utility-bro_yaml_utility os:ubuntu-latest;pub-cache-hosted;sdk:stable os:ubuntu-latest;pub-cache-hosted os:ubuntu-latest @@ -112,6 +112,32 @@ jobs: run: "dart pub global activate pana && pana --line-length 100 ." if: "always() && steps.bro_file_utility_pub_upgrade.conclusion == 'success'" working-directory: bro_file_utility + - id: bro_firebase_core_pub_upgrade + name: bro_firebase_core; flutter pub upgrade + run: flutter pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: bro_firebase_core + - name: "bro_firebase_core; flutter analyze --fatal-infos ." + run: flutter analyze --fatal-infos . + if: "always() && steps.bro_firebase_core_pub_upgrade.conclusion == 'success'" + working-directory: bro_firebase_core + - name: "bro_firebase_core; dart pub global activate pana && pana --line-length 100 ." + run: "dart pub global activate pana && pana --line-length 100 ." + if: "always() && steps.bro_firebase_core_pub_upgrade.conclusion == 'success'" + working-directory: bro_firebase_core + - id: bro_firebase_crashlytics_pub_upgrade + name: bro_firebase_crashlytics; flutter pub upgrade + run: flutter pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: bro_firebase_crashlytics + - name: "bro_firebase_crashlytics; flutter analyze --fatal-infos ." + run: flutter analyze --fatal-infos . + if: "always() && steps.bro_firebase_crashlytics_pub_upgrade.conclusion == 'success'" + working-directory: bro_firebase_crashlytics + - name: "bro_firebase_crashlytics; dart pub global activate pana && pana --line-length 100 ." + run: "dart pub global activate pana && pana --line-length 100 ." + if: "always() && steps.bro_firebase_crashlytics_pub_upgrade.conclusion == 'success'" + working-directory: bro_firebase_crashlytics - id: bro_global_manager_pub_upgrade name: bro_global_manager; flutter pub upgrade run: flutter pub upgrade @@ -151,6 +177,19 @@ jobs: run: "dart pub global activate pana && pana --line-length 100 ." if: "always() && steps.bro_logger_manager_pub_upgrade.conclusion == 'success'" working-directory: bro_logger_manager + - id: bro_platform_utility_pub_upgrade + name: bro_platform_utility; flutter pub upgrade + run: flutter pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: bro_platform_utility + - name: "bro_platform_utility; flutter analyze --fatal-infos ." + run: flutter analyze --fatal-infos . + if: "always() && steps.bro_platform_utility_pub_upgrade.conclusion == 'success'" + working-directory: bro_platform_utility + - name: "bro_platform_utility; dart pub global activate pana && pana --line-length 100 ." + run: "dart pub global activate pana && pana --line-length 100 ." + if: "always() && steps.bro_platform_utility_pub_upgrade.conclusion == 'success'" + working-directory: bro_platform_utility - id: bro_types_utility_pub_upgrade name: bro_types_utility; flutter pub upgrade run: flutter pub upgrade @@ -178,16 +217,16 @@ jobs: if: "always() && steps.bro_yaml_utility_pub_upgrade.conclusion == 'success'" working-directory: bro_yaml_utility job_003: - name: "unit_test; PKGS: bro_abstract_logger, bro_abstract_manager, bro_config_manager, bro_file_utility, bro_global_manager, bro_list_utility, bro_logger_manager, bro_types_utility, bro_yaml_utility; `flutter test --flavor test .`" + name: "unit_test; PKGS: bro_abstract_logger, bro_abstract_manager, bro_config_manager, bro_file_utility, bro_global_manager, bro_list_utility, bro_logger_manager, bro_platform_utility, bro_types_utility, bro_yaml_utility; `flutter test --flavor test .`" runs-on: ubuntu-latest steps: - name: Cache Pub hosted dependencies uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 with: path: "~/.pub-cache/hosted" - key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:bro_abstract_logger-bro_abstract_manager-bro_config_manager-bro_file_utility-bro_global_manager-bro_list_utility-bro_logger_manager-bro_types_utility-bro_yaml_utility;commands:test" + key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:bro_abstract_logger-bro_abstract_manager-bro_config_manager-bro_file_utility-bro_global_manager-bro_list_utility-bro_logger_manager-bro_platform_utility-bro_types_utility-bro_yaml_utility;commands:test" restore-keys: | - os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:bro_abstract_logger-bro_abstract_manager-bro_config_manager-bro_file_utility-bro_global_manager-bro_list_utility-bro_logger_manager-bro_types_utility-bro_yaml_utility + os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:bro_abstract_logger-bro_abstract_manager-bro_config_manager-bro_file_utility-bro_global_manager-bro_list_utility-bro_logger_manager-bro_platform_utility-bro_types_utility-bro_yaml_utility os:ubuntu-latest;pub-cache-hosted;sdk:stable os:ubuntu-latest;pub-cache-hosted os:ubuntu-latest @@ -261,6 +300,15 @@ jobs: run: flutter test --flavor test . if: "always() && steps.bro_logger_manager_pub_upgrade.conclusion == 'success'" working-directory: bro_logger_manager + - id: bro_platform_utility_pub_upgrade + name: bro_platform_utility; flutter pub upgrade + run: flutter pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: bro_platform_utility + - name: "bro_platform_utility; flutter test --flavor test ." + run: flutter test --flavor test . + if: "always() && steps.bro_platform_utility_pub_upgrade.conclusion == 'success'" + working-directory: bro_platform_utility - id: bro_types_utility_pub_upgrade name: bro_types_utility; flutter pub upgrade run: flutter pub upgrade diff --git a/bro_abstract_logger/CHANGELOG.md b/bro_abstract_logger/CHANGELOG.md index 20df858..f4720e5 100644 --- a/bro_abstract_logger/CHANGELOG.md +++ b/bro_abstract_logger/CHANGELOG.md @@ -11,6 +11,7 @@ SPDX-License-Identifier: MIT - Add log level parsing from string. - Add flutter error management to the abstract manager. +- Add log format utility class to offer a way to format the log message. ## 1.0.1 diff --git a/bro_abstract_logger/lib/bro_abstract_logger.dart b/bro_abstract_logger/lib/bro_abstract_logger.dart index 4bb4a90..e18464c 100644 --- a/bro_abstract_logger/lib/bro_abstract_logger.dart +++ b/bro_abstract_logger/lib/bro_abstract_logger.dart @@ -12,3 +12,4 @@ export 'src/services/abstract_logger_manager.dart'; export 'src/services/abstract_multi_logger_manager.dart'; export 'src/services/void_logger_manager.dart'; export 'src/types/logs_level.dart'; +export 'src/utilities/log_format_utility.dart'; diff --git a/bro_abstract_logger/lib/src/helpers/default_logger_helper.dart b/bro_abstract_logger/lib/src/helpers/default_logger_helper.dart deleted file mode 100644 index d3dbec1..0000000 --- a/bro_abstract_logger/lib/src/helpers/default_logger_helper.dart +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-FileCopyrightText: 2024 Benoit Rolandeau -// -// SPDX-License-Identifier: MIT - -import 'package:bro_abstract_logger/src/helpers/default_print_logger.dart'; -import 'package:bro_abstract_logger/src/helpers/logger_helper.dart'; -import 'package:bro_abstract_logger/src/helpers/void_print_logger.dart'; - -/// This is a default logger helper that uses one of the default print loggers. -class DefaultLoggerHelper extends LoggerHelper { - /// Create a default logger helper. - /// - /// If [printLogs] is true, the logger will use [DefaultPrintLogger.instance]. - /// Otherwise, it will use [VoidPrintLogger.instance] (which print nothing). - DefaultLoggerHelper({ - bool printLogs = true, - }) : super( - logger: printLogs ? DefaultPrintLogger.instance : VoidPrintLogger.instance, - ); -} diff --git a/bro_abstract_logger/lib/src/helpers/default_print_logger.dart b/bro_abstract_logger/lib/src/helpers/default_print_logger.dart index 6dfdfb4..1e6de13 100644 --- a/bro_abstract_logger/lib/src/helpers/default_print_logger.dart +++ b/bro_abstract_logger/lib/src/helpers/default_print_logger.dart @@ -2,8 +2,7 @@ // // SPDX-License-Identifier: MIT -import 'package:bro_abstract_logger/src/mixins/mixin_external_logger.dart'; -import 'package:bro_abstract_logger/src/types/logs_level.dart'; +import 'package:bro_abstract_logger/bro_abstract_logger.dart'; import 'package:flutter/foundation.dart'; /// A default logger that print logs to the console. @@ -22,9 +21,6 @@ class DefaultPrintLogger with MixinExternalLogger { return _instance!; } - /// This is the default separator used to separate categories in the log message. - static const String _defaultCategorySeparator = "/"; - /// This is the default prefix used to add a category to the log message. /// /// This is used to see when we use the [DefaultPrintLogger]. @@ -48,9 +44,10 @@ class DefaultPrintLogger with MixinExternalLogger { return; } - _debugPrint(_formatMessage( + _debugPrint(LogFormatUtility.formatLogMessages( + time: DateTime.now(), level: level, - categories: categories, + categories: [_defaultCategoryPrefix, ...categories], message: message, )); } @@ -70,42 +67,24 @@ class DefaultPrintLogger with MixinExternalLogger { return; } - _debugPrint(_formatMessage( + _debugPrint(LogFormatUtility.formatLogMessages( + time: DateTime.now(), level: isFatal ? LogsLevel.fatal : LogsLevel.error, - categories: categories, - message: exception, + categories: [_defaultCategoryPrefix, ...categories], + exception: exception, + stackTrace: stackTrace, )); - if (stackTrace != null) { - _debugPrint(_formatMessage( - level: isFatal ? LogsLevel.fatal : LogsLevel.error, - categories: categories, - message: stackTrace, - )); - } } /// This method tests if a log is loggable thanks to the [minLevel]. bool _testIfLoggable(LogsLevel level) => level.index >= minLevel.index; - /// This method formats a log message with the provided [level], [categories] and [message]. - /// - /// It adds a timestamp to the log message. - String _formatMessage({ - required LogsLevel level, - required List categories, - // We use dynamic here to be able to log any type of exception - // ignore: avoid_annotating_with_dynamic - required dynamic message, - }) => - "${DateTime.now().toUtc().toIso8601String()} - [${level.name.toLowerCase()}] [${[ - _defaultCategoryPrefix, - ...categories, - ].join(_defaultCategorySeparator)}]: $message"; - /// This uses the `print` function to log messages, only in debug mode. - void _debugPrint(Object? message) { + void _debugPrint(List messages) { if (kDebugMode) { - print(message); + for (final message in messages) { + print(message); + } } } diff --git a/bro_abstract_logger/lib/src/helpers/logger_helper.dart b/bro_abstract_logger/lib/src/helpers/logger_helper.dart index e8075a3..d2ac5c5 100644 --- a/bro_abstract_logger/lib/src/helpers/logger_helper.dart +++ b/bro_abstract_logger/lib/src/helpers/logger_helper.dart @@ -2,7 +2,9 @@ // // SPDX-License-Identifier: MIT +import 'package:bro_abstract_logger/src/helpers/default_print_logger.dart'; import 'package:bro_abstract_logger/src/helpers/sub_logger_helper.dart'; +import 'package:bro_abstract_logger/src/helpers/void_print_logger.dart'; import 'package:bro_abstract_logger/src/mixins/mixin_external_logger.dart'; import 'package:bro_abstract_logger/src/types/logs_level.dart'; import 'package:flutter/foundation.dart'; @@ -48,6 +50,17 @@ class LoggerHelper { ], _logger = logger; + /// Create a default logger helper. + /// + /// If [printLogs] is true, the logger will use [DefaultPrintLogger.instance]. + /// Otherwise, it will use [VoidPrintLogger.instance] (which print nothing). + factory LoggerHelper.initWithDefaultLogger({ + bool printLogs = true, + }) => + LoggerHelper( + logger: printLogs ? DefaultPrintLogger.instance : VoidPrintLogger.instance, + ); + /// This is the constructor for the sub-logger. /// /// [categories] has to contain the parent categories. diff --git a/bro_abstract_logger/lib/src/helpers/multi_logger_helper.dart b/bro_abstract_logger/lib/src/helpers/multi_logger_helper.dart index 24094d3..d14ab77 100644 --- a/bro_abstract_logger/lib/src/helpers/multi_logger_helper.dart +++ b/bro_abstract_logger/lib/src/helpers/multi_logger_helper.dart @@ -3,45 +3,17 @@ // SPDX-License-Identifier: MIT import 'package:bro_abstract_logger/src/helpers/logger_helper.dart'; -import 'package:bro_abstract_logger/src/types/logs_level.dart'; +import 'package:bro_abstract_logger/src/helpers/multi_print_logger.dart'; /// A [LoggerHelper] that logs to multiple [LoggerHelper]s. class MultiLoggerHelper extends LoggerHelper { /// The list of [LoggerHelper]s to log to. - final List _loggers; + final List loggers; /// Create a [MultiLoggerHelper] with multiple [LoggerHelper]s. MultiLoggerHelper({ - required List loggers, - }) : _loggers = loggers, - super.subLogger( - categories: [], - minLevel: null, + required this.loggers, + }) : super( + logger: MultiPrintLogger(loggers: loggers), ); - - /// {@macro bro_abstract_logger.LoggerHelper.log} - @override - void log(LogsLevel level, String message) { - for (final logger in _loggers) { - logger.log(level, message); - } - } - - /// {@macro bro_abstract_logger.LoggerHelper.logErrorWithException} - @override - void logErrorWithException( - // We use dynamic here to be able to log any type of exception - // ignore: avoid_annotating_with_dynamic - dynamic exception, { - bool isFatal = false, - StackTrace? stackTrace, - }) { - for (final logger in _loggers) { - logger.logErrorWithException( - exception, - isFatal: isFatal, - stackTrace: stackTrace, - ); - } - } } diff --git a/bro_abstract_logger/lib/src/helpers/multi_print_logger.dart b/bro_abstract_logger/lib/src/helpers/multi_print_logger.dart new file mode 100644 index 0000000..bf8133c --- /dev/null +++ b/bro_abstract_logger/lib/src/helpers/multi_print_logger.dart @@ -0,0 +1,51 @@ +// SPDX-FileCopyrightText: 2025 Benoit Rolandeau +// +// SPDX-License-Identifier: MIT + +import 'package:bro_abstract_logger/bro_abstract_logger.dart'; + +/// This printer allows to print to multiple [LoggerHelper]s. +class MultiPrintLogger with MixinExternalLogger { + /// The list of [LoggerHelper]s to log to. + final List _otherLoggers; + + /// Create a [MultiPrintLogger] with multiple [LoggerHelper]s. + MultiPrintLogger({ + required List loggers, + }) : _otherLoggers = loggers; + + /// {@macro bro_abstract_logger.MixinExternalLogger.log} + @override + void log( + LogsLevel level, + String message, { + List categories = const [], + }) { + for (final logger in _otherLoggers) { + logger.log(level, message); + } + } + + /// {@macro bro_abstract_logger.MixinExternalLogger.logErrorWithException} + @override + void logErrorWithException( + // We use dynamic here to be able to log any type of exception + // ignore: avoid_annotating_with_dynamic + dynamic exception, { + StackTrace? stackTrace, + bool isFatal = false, + List categories = const [], + }) { + for (final logger in _otherLoggers) { + logger.logErrorWithException( + exception, + stackTrace: stackTrace, + isFatal: isFatal, + ); + } + } + + /// {@macro bro_abstract_logger.MixinExternalLogger.dispose} + @override + Future dispose() async {} +} diff --git a/bro_abstract_logger/lib/src/services/abstract_logger_manager.dart b/bro_abstract_logger/lib/src/services/abstract_logger_manager.dart index 8382dab..836c87a 100644 --- a/bro_abstract_logger/lib/src/services/abstract_logger_manager.dart +++ b/bro_abstract_logger/lib/src/services/abstract_logger_manager.dart @@ -2,16 +2,18 @@ // // SPDX-License-Identifier: MIT -import 'package:bro_abstract_logger/src/helpers/default_logger_helper.dart'; import 'package:bro_abstract_logger/src/helpers/logger_helper.dart'; import 'package:bro_abstract_logger/src/mixins/mixin_external_logger.dart'; import 'package:bro_abstract_manager/bro_abstract_manager.dart'; import 'package:flutter/foundation.dart'; +/// This is the type of the Platform error handler. +typedef PlatformErrorHandler = bool Function(Object exception, StackTrace stackTrace); + /// This is an abstract builder for logger managers. abstract class AbsLoggerBuilder extends AbsManagerBuilder { /// Class constructor. - const AbsLoggerBuilder(super.managerFactory); + const AbsLoggerBuilder(); } /// This is the abstract class for logger managers. @@ -27,7 +29,7 @@ abstract class AbstractLoggerManager extends AbsWithLifeCycle { FlutterExceptionHandler? _defaultFlutterErrorHandler; /// The default platform error handler. - bool Function(Object exception, StackTrace stackTrace)? _defaultPlatformErrorHandler; + PlatformErrorHandler? _defaultPlatformErrorHandler; /// The logger helper to use with the logger manager. final LoggerHelper loggerHelper; @@ -44,7 +46,7 @@ abstract class AbstractLoggerManager extends AbsWithLifeCycle { AbstractLoggerManager({ bool printLogsByDefault = true, this.registerFlutterNonManagedErrors = false, - }) : loggerHelper = DefaultLoggerHelper( + }) : loggerHelper = LoggerHelper.initWithDefaultLogger( printLogs: printLogsByDefault, ); @@ -62,6 +64,13 @@ abstract class AbstractLoggerManager extends AbsWithLifeCycle { _externalLogger = await getExternalLogger(); loggerHelper.updateLogger(_externalLogger); + registerFlutterNonManagedErrorsProcess(); + } + + /// This method registers the Flutter and platform non-managed errors if the + /// [registerFlutterNonManagedErrors] is true. + @protected + void registerFlutterNonManagedErrorsProcess() { if (registerFlutterNonManagedErrors) { _defaultFlutterErrorHandler = FlutterError.onError; _defaultPlatformErrorHandler = PlatformDispatcher.instance.onError; @@ -73,7 +82,7 @@ abstract class AbstractLoggerManager extends AbsWithLifeCycle { /// This method is called when a Flutter error is thrown. /// /// It logs a fatal error with the exception and the stack trace. - void _manageFlutterError(FlutterErrorDetails details) => _externalLogger.logErrorWithException( + void _manageFlutterError(FlutterErrorDetails details) => loggerHelper.logErrorWithException( details.exception, stackTrace: details.stack, isFatal: true, diff --git a/bro_abstract_logger/lib/src/services/abstract_multi_logger_manager.dart b/bro_abstract_logger/lib/src/services/abstract_multi_logger_manager.dart index 0f3fb04..dc9bb80 100644 --- a/bro_abstract_logger/lib/src/services/abstract_multi_logger_manager.dart +++ b/bro_abstract_logger/lib/src/services/abstract_multi_logger_manager.dart @@ -4,7 +4,6 @@ import 'package:bro_abstract_logger/bro_abstract_logger.dart'; import 'package:bro_abstract_logger/src/helpers/multi_logger_helper.dart'; -import 'package:bro_abstract_manager/bro_abstract_manager.dart'; import 'package:flutter/widgets.dart'; /// This is the abstract builder for a multi logger manager. @@ -16,24 +15,32 @@ import 'package:flutter/widgets.dart'; /// /// {@macro bro_abstract_logger.AbstractMultiLoggerManager.errorRegistration} abstract class AbsMultiLoggerBuilder - extends AbsManagerBuilder { + extends AbsLoggerBuilder { /// The list of loggers builders. final List _loggersBuilders; /// Class constructor. /// /// {@template bro_abstract_logger.AbsMultiLoggerBuilder.constructor.params} - /// The [mainManagerFactory] is the factory to create the multi logger manager. /// The [loggersBuilders] is the list of builders to create the linked logger managers. /// {@endtemplate} - AbsMultiLoggerBuilder({ - required L Function(List loggerManager) mainManagerFactory, + const AbsMultiLoggerBuilder({ required List loggersBuilders, }) : _loggersBuilders = loggersBuilders, - super(() => _multiFactory( - mainManagerFactory: mainManagerFactory, - loggersBuilders: loggersBuilders, - )); + super(); + + /// {@template bro_abstract_logger.AbsMultiLoggerBuilder.createMultiLoggerManager} + /// Create the multi logger manager with the [loggerManagers] already created. + /// {@endtemplate} + @protected + L createMultiLoggerManager(List loggerManagers); + + /// {@macro bro_abstract_manager.AbsManagerBuilder.create} + @override + L create() => _multiFactory( + mainManagerFactory: createMultiLoggerManager, + loggersBuilders: _loggersBuilders, + ); /// Create the multi logger manager. /// @@ -44,7 +51,7 @@ abstract class AbsMultiLoggerBuilder }) { final managers = []; for (final builder in loggersBuilders) { - managers.add(builder.managerFactory()); + managers.add(builder.create()); } return mainManagerFactory(managers); @@ -88,6 +95,8 @@ abstract class AbstractMultiLoggerManager extends AbstractLoggerManager { // ignore: must_call_super Future initLifeCycle() async { await Future.wait(_loggersManager.map((manager) => manager.initLifeCycle())); + + registerFlutterNonManagedErrorsProcess(); } /// {@macro bro_abstract_logger.AbstractLoggerManager.getExternalLogger} @@ -110,6 +119,16 @@ abstract class AbstractMultiLoggerManager extends AbstractLoggerManager { await super.initAfterViewBuilt(context); } + /// Try to get a logger manager of type [T] from the list of linked managers. + T? tryToGetManager() { + final managerIdx = _loggersManager.indexWhere((manager) => manager is T); + if (managerIdx < 0) { + loggerHelper.warn("The wanted manager $T is not in the list of linked managers."); + return null; + } + return _loggersManager[managerIdx] as T; + } + /// {@macro bro_abstract_manager.AbsWithLifeCycle.disposeLifeCycle} @override Future disposeLifeCycle() async { diff --git a/bro_abstract_logger/lib/src/services/void_logger_manager.dart b/bro_abstract_logger/lib/src/services/void_logger_manager.dart index 262b858..7c9aaef 100644 --- a/bro_abstract_logger/lib/src/services/void_logger_manager.dart +++ b/bro_abstract_logger/lib/src/services/void_logger_manager.dart @@ -7,7 +7,11 @@ import 'package:bro_abstract_logger/bro_abstract_logger.dart'; /// This class is used to build a [VoidLoggerManager]. class VoidLoggerBuilder extends AbsLoggerBuilder { /// Class constructor. - const VoidLoggerBuilder() : super(VoidLoggerManager.new); + const VoidLoggerBuilder() : super(); + + /// {@macro bro_abstract_manager.AbsManagerBuilder.create} + @override + VoidLoggerManager create() => VoidLoggerManager(); /// {@macro bro_abstract_manager.AbsManagerBuilder.getDependencies} @override diff --git a/bro_abstract_logger/lib/src/utilities/log_format_utility.dart b/bro_abstract_logger/lib/src/utilities/log_format_utility.dart new file mode 100644 index 0000000..bab09cb --- /dev/null +++ b/bro_abstract_logger/lib/src/utilities/log_format_utility.dart @@ -0,0 +1,86 @@ +// SPDX-FileCopyrightText: 2025 Benoit Rolandeau +// +// SPDX-License-Identifier: MIT + +import 'package:bro_abstract_logger/bro_abstract_logger.dart'; + +/// This is a utility class that provides methods to format log messages. +abstract final class LogFormatUtility { + /// This is the separator used to separate categories in the log message. + static const String categorySeparator = "/"; + + /// This method formats the categories into a string. + static String formatCategories(List categories) => categories.join(categorySeparator); + + /// This method formats the log messages into a list of strings which can be logged by an + /// external logger. + /// + /// The [message] is the main message to log. The [exception] and [stackTrace] are optional and + /// will be logged if provided in different lines + /// The other parameters are used to add more context to the log message. + /// If one of the parameters is null, it will be ignored in the displayed lines. + /// + /// The format is the following: + /// + /// - [time] - [level] [categories]: [message] + /// - [time] - [level] [categories]: [exception] (_optional_) + /// - [time] - [level] [categories]: [stackTrace] (_optional_) + /// + /// The prefix can be: + /// + /// - [time] - [level] [categories]: + /// - [time] - [level]: + /// - [time] - [categories]: + /// - [time]: + /// - [level] [categories]: + /// - [level]: + /// - [categories]: + /// + /// Example with all paramters: + /// + /// > 2025-01-08T11:50:38.470987Z - [info] [default/other]: Global manager initialized. + static List formatLogMessages({ + // We use dynamic here to be able to log any type of message + // ignore: avoid_annotating_with_dynamic + dynamic message, + List categories = const [], + LogsLevel? level, + DateTime? time, + StackTrace? stackTrace, + // We use dynamic here to be able to log any type of exception + // ignore: avoid_annotating_with_dynamic + dynamic exception, + }) { + final prefixElems = []; + + if (time != null) { + prefixElems.add(time.toUtc().toIso8601String()); + prefixElems.add("-"); + } + + if (level != null) { + prefixElems.add("[${level.name.toLowerCase()}]"); + } + + if (categories.isNotEmpty) { + prefixElems.add("[${formatCategories(categories)}]"); + } + + final prefixLength = prefixElems.length; + var prefixMsg = ""; + if (prefixLength > 0) { + if (time != null && prefixLength == 2) { + // We remove the time padding to avoid having a space or a glyph before the message. + prefixElems.removeLast(); + } + + prefixMsg = "${prefixElems.join(" ")}: "; + } + + return [ + if (message != null) "$prefixMsg$message", + if (exception != null) "$prefixMsg$exception", + if (stackTrace != null) "$prefixMsg$stackTrace", + ]; + } +} diff --git a/bro_abstract_logger/pubspec.yaml b/bro_abstract_logger/pubspec.yaml index 39c895a..3f3e476 100644 --- a/bro_abstract_logger/pubspec.yaml +++ b/bro_abstract_logger/pubspec.yaml @@ -23,7 +23,7 @@ dependencies: flutter: sdk: flutter - bro_abstract_manager: ^1.0.1 + bro_abstract_manager: ^1.1.0 dev_dependencies: flutter_test: diff --git a/bro_abstract_logger/test/abstract_multi_logger_manager_test.dart b/bro_abstract_logger/test/abstract_multi_logger_manager_test.dart index 843f872..79c6cae 100644 --- a/bro_abstract_logger/test/abstract_multi_logger_manager_test.dart +++ b/bro_abstract_logger/test/abstract_multi_logger_manager_test.dart @@ -8,6 +8,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'mock/helpers/test_print_logger.dart'; import 'mock/models/test_log_model.dart'; import 'mock/services/a_logger_manager.dart'; +import 'mock/services/b_logger_manager.dart'; import 'mock/services/b_multi_logger_manager.dart'; /// Test the [AbstractMultiLoggerManager] behaviours. @@ -15,12 +16,22 @@ void main() { test('Create MultiLoggerManager', () async { final builder = BMultiBuilder(loggersBuilders: [ ALoggerBuilder(), - ALoggerBuilder(), + BLoggerBuilder(), ]); final manager = await builder.build(); expect(manager, isNotNull); + expect( + manager.tryToGetManager(), + isNotNull, + reason: 'Test if the logger manager A has been correctly created', + ); + expect( + manager.tryToGetManager(), + isNotNull, + reason: 'Test if the logger manager B has been correctly created', + ); }); test('Log before MultiLoggerManager init', () { @@ -62,23 +73,15 @@ void main() { }); test('Log after MultiLoggerManager init', () async { - final managerA = ALoggerManager.fromLoggerHelper( - loggerHelper: LoggerHelper( - logger: TestPrintLogger(), - ), - ); - final managerB = ALoggerManager.fromLoggerHelper( - loggerHelper: LoggerHelper( - logger: TestPrintLogger(), - ), - ); - - final multiManager = BMultiLoggerManager([ - managerA, - managerB, + final builder = BMultiBuilder(loggersBuilders: [ + ALoggerBuilder(), + BLoggerBuilder(), ]); - await multiManager.initLifeCycle(); + final multiManager = await builder.build(); + + final managerA = multiManager.tryToGetManager()!; + final managerB = multiManager.tryToGetManager()!; const log1 = TestLogModel( message: 'Test log 1', diff --git a/bro_abstract_logger/test/log_format_utility_test.dart b/bro_abstract_logger/test/log_format_utility_test.dart new file mode 100644 index 0000000..67ada38 --- /dev/null +++ b/bro_abstract_logger/test/log_format_utility_test.dart @@ -0,0 +1,119 @@ +// SPDX-FileCopyrightText: 2024 Benoit Rolandeau +// +// SPDX-License-Identifier: MIT + +import 'package:bro_abstract_logger/bro_abstract_logger.dart'; +import 'package:flutter_test/flutter_test.dart'; + +/// Test the [LogFormatUtility] class. +void main() { + group("Test formatLogMessages method", () { + test('Test different exporting', () async { + final time = DateTime.now().toUtc(); + expect( + LogFormatUtility.formatLogMessages( + level: LogsLevel.info, + categories: ['default', "other"], + message: 'Test log', + time: time, + ), + [ + '${time.toIso8601String()} - [info] [default/other]: Test log', + ], + reason: "Test one message line", + ); + expect( + LogFormatUtility.formatLogMessages( + level: LogsLevel.info, + message: 'Test log', + time: time, + ), + [ + '${time.toIso8601String()} - [info]: Test log', + ], + reason: "Test one message line without categories", + ); + expect( + LogFormatUtility.formatLogMessages( + categories: ['default', "other"], + message: 'Test log', + time: time, + ), + [ + '${time.toIso8601String()} - [default/other]: Test log', + ], + reason: "Test one message line without level", + ); + expect( + LogFormatUtility.formatLogMessages( + message: 'Test log', + time: time, + ), + [ + '${time.toIso8601String()}: Test log', + ], + reason: "Test one message line without level", + ); + expect( + LogFormatUtility.formatLogMessages( + level: LogsLevel.info, + categories: ['default', "other"], + message: 'Test log', + ), + [ + '[info] [default/other]: Test log', + ], + reason: "Test one message line without time", + ); + expect( + LogFormatUtility.formatLogMessages( + level: LogsLevel.info, + message: 'Test log', + ), + [ + '[info]: Test log', + ], + reason: "Test one message line without time and categories", + ); + expect( + LogFormatUtility.formatLogMessages( + categories: ['default', "other"], + message: 'Test log', + ), + [ + '[default/other]: Test log', + ], + reason: "Test one message line without time and level", + ); + expect( + LogFormatUtility.formatLogMessages( + message: 'Test log', + ), + [ + 'Test log', + ], + reason: "Test one message line with only the message", + ); + }); + test('Test different exporting with exception and stacktrace', () async { + final time = DateTime.now().toUtc(); + final currentStackTrace = StackTrace.current; + expect( + LogFormatUtility.formatLogMessages( + level: LogsLevel.info, + categories: ['default', "other"], + message: 'Test log', + time: time, + exception: Exception('Test exception'), + stackTrace: currentStackTrace, + ), + [ + '${time.toIso8601String()} - [info] [default/other]: Test log', + '${time.toIso8601String()} - [info] [default/other]: Exception: Test exception', + '${time.toIso8601String()} - [info] [default/other]: $currentStackTrace', + ], + reason: "Test one message line", + ); + }); + }); +} diff --git a/bro_abstract_logger/test/logger_helper_test.dart b/bro_abstract_logger/test/logger_helper_test.dart index 5fff398..0f0459b 100644 --- a/bro_abstract_logger/test/logger_helper_test.dart +++ b/bro_abstract_logger/test/logger_helper_test.dart @@ -11,21 +11,12 @@ import 'mock/models/test_log_model.dart'; /// Test the logger helper behaviours. void main() { test('Test to print with default log helper', () { - bool success; - - try { - expect( - () => DefaultPrintLogger.instance.info('Test log'), - prints(matches("[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{6}Z " - r'- \[info\] \[default\]: Test log\n')), - reason: "Expect to print the log", - ); - success = true; - } catch (e) { - success = false; - } - - expect(success, true, reason: "Expect to have no crash"); + expect( + () => DefaultPrintLogger.instance.info('Test log'), + prints(matches("[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{6}Z " + r'- \[info\] \[default\]: Test log\n')), + reason: "Expect to print the log", + ); }); group("Test the logger helper parameters", () { diff --git a/bro_abstract_logger/test/mock/services/a_logger_manager.dart b/bro_abstract_logger/test/mock/services/a_logger_manager.dart index 8f7fd66..03a32fb 100644 --- a/bro_abstract_logger/test/mock/services/a_logger_manager.dart +++ b/bro_abstract_logger/test/mock/services/a_logger_manager.dart @@ -8,8 +8,8 @@ import '../helpers/test_print_logger.dart'; /// This is the builder to create the [ALoggerManager]. class ALoggerBuilder extends AbsLoggerBuilder { - /// Create the [ALoggerBuilder]. - ALoggerBuilder() : super(ALoggerManager.new); + @override + ALoggerManager create() => ALoggerManager(); /// {@macro bro_abstract_manager.AbsManagerBuilder.getDependencies} @override diff --git a/bro_abstract_logger/test/mock/services/b_logger_manager.dart b/bro_abstract_logger/test/mock/services/b_logger_manager.dart new file mode 100644 index 0000000..1e0d6a7 --- /dev/null +++ b/bro_abstract_logger/test/mock/services/b_logger_manager.dart @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2024 Benoit Rolandeau +// +// SPDX-License-Identifier: MIT + +import 'package:bro_abstract_logger/bro_abstract_logger.dart'; + +import '../helpers/test_print_logger.dart'; + +/// This is the builder to create the [BLoggerManager]. +class BLoggerBuilder extends AbsLoggerBuilder { + @override + BLoggerManager create() => BLoggerManager(); + + /// {@macro bro_abstract_manager.AbsManagerBuilder.getDependencies} + @override + Iterable getDependencies() => []; +} + +/// This is the manager used to test the logging system. +class BLoggerManager extends AbstractLoggerManager { + /// Create the [BLoggerManager]. + BLoggerManager({ + super.registerFlutterNonManagedErrors, + }) : super(); + + /// Create the [BLoggerManager] from the [loggerHelper]. + BLoggerManager.fromLoggerHelper({ + required super.loggerHelper, + super.registerFlutterNonManagedErrors, + }) : super.fromLoggerHelper(); + + /// {@macro bro_abstract_logger.AbstractLoggerManager.getExternalLogger} + @override + Future getExternalLogger() async => TestPrintLogger(); +} diff --git a/bro_abstract_logger/test/mock/services/b_multi_logger_manager.dart b/bro_abstract_logger/test/mock/services/b_multi_logger_manager.dart index 8020ee3..ada0f2c 100644 --- a/bro_abstract_logger/test/mock/services/b_multi_logger_manager.dart +++ b/bro_abstract_logger/test/mock/services/b_multi_logger_manager.dart @@ -9,7 +9,11 @@ class BMultiBuilder extends AbsMultiLoggerBuilder { /// Create the [BMultiBuilder]. BMultiBuilder({ required super.loggersBuilders, - }) : super(mainManagerFactory: BMultiLoggerManager.new); + }) : super(); + + @override + BMultiLoggerManager createMultiLoggerManager(List loggerManager) => + BMultiLoggerManager(loggerManager); } /// This is the multi logger manager used to test the logging system. diff --git a/bro_abstract_manager/CHANGELOG.md b/bro_abstract_manager/CHANGELOG.md index 65d43db..e308dff 100644 --- a/bro_abstract_manager/CHANGELOG.md +++ b/bro_abstract_manager/CHANGELOG.md @@ -7,6 +7,13 @@ SPDX-License-Identifier: MIT +## 1.1.0 + +- Add new methods to the builder classes to know if the manager supports (or not) the current + platform. +- Transform the async factory passed to the builder class constructor into a method to override by + derived class. + ## 1.0.1 - Update pubspec.yaml file to be more compliant with dart pub.dev requirements. diff --git a/bro_abstract_manager/lib/bro_abstract_manager.dart b/bro_abstract_manager/lib/bro_abstract_manager.dart index d4ad191..0d4bf44 100644 --- a/bro_abstract_manager/lib/bro_abstract_manager.dart +++ b/bro_abstract_manager/lib/bro_abstract_manager.dart @@ -4,5 +4,9 @@ library; -export 'src/abs_manager_builder.dart'; -export 'src/abs_with_life_cycle.dart'; +export 'package:bro_platform_utility/bro_platform_utility.dart' show PlatformType; + +export 'src/errors/platform_not_supported_by_manager_error.dart'; +export 'src/services/abs_manager_builder.dart'; +export 'src/services/abs_with_life_cycle.dart'; +export 'src/types/wrong_platform_behavior.dart'; diff --git a/bro_abstract_manager/lib/src/abs_manager_builder.dart b/bro_abstract_manager/lib/src/abs_manager_builder.dart deleted file mode 100644 index 1d69ca9..0000000 --- a/bro_abstract_manager/lib/src/abs_manager_builder.dart +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-FileCopyrightText: 2024 Benoit Rolandeau -// -// SPDX-License-Identifier: MIT - -import 'package:bro_abstract_manager/bro_abstract_manager.dart'; -import 'package:flutter/widgets.dart'; - -/// This is an abstract class to build a manager with a lifecycle. -/// -/// This is used to build managers for get it package. -abstract class AbsManagerBuilder { - /// The factory used to create the manager. - final Manager Function() managerFactory; - - /// Class constructor. - const AbsManagerBuilder(this.managerFactory); - - /// {@template bro_abstract_manager.AbsManagerBuilder.build} - /// Build the manager and call the [AbsWithLifeCycle.initLifeCycle] method. - /// {@endtemplate} - @mustCallSuper - Future build({ - Manager? managerToInit, - }) async { - final manager = managerToInit ?? managerFactory(); - await manager.initLifeCycle(); - return manager; - } - - /// {@template bro_abstract_manager.AbsManagerBuilder.getDependencies} - /// Get the dependencies of the manager. - /// - /// [Type] is the type of the dependencies and must be a subclass of [AbsWithLifeCycle]. - /// {@endtemplate} - @mustCallSuper - Iterable getDependencies(); -} diff --git a/bro_abstract_manager/lib/src/errors/platform_not_supported_by_manager_error.dart b/bro_abstract_manager/lib/src/errors/platform_not_supported_by_manager_error.dart new file mode 100644 index 0000000..cee5be6 --- /dev/null +++ b/bro_abstract_manager/lib/src/errors/platform_not_supported_by_manager_error.dart @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2024 Benoit Rolandeau +// +// SPDX-License-Identifier: MIT + +import 'package:bro_platform_utility/bro_platform_utility.dart'; + +/// An error throwns when the platform is not supported by the manager. +class PlatformNotSupportedByManagerError extends Error { + /// The current platform of the application + final PlatformType currentPlatform; + + /// The list of platforms supported by the manager + final List supportedPlatforms; + + /// Create a [PlatformNotSupportedByManagerError] with the current platform and the supported + /// platforms. + PlatformNotSupportedByManagerError({ + required this.currentPlatform, + required this.supportedPlatforms, + }); + + /// Get a string representation of the error. + @override + String toString() => 'The platform $currentPlatform is not supported by the manager. ' + 'The supported platforms by the manager are $supportedPlatforms.'; +} diff --git a/bro_abstract_manager/lib/src/services/abs_manager_builder.dart b/bro_abstract_manager/lib/src/services/abs_manager_builder.dart new file mode 100644 index 0000000..3c4ba82 --- /dev/null +++ b/bro_abstract_manager/lib/src/services/abs_manager_builder.dart @@ -0,0 +1,113 @@ +// SPDX-FileCopyrightText: 2024 Benoit Rolandeau +// +// SPDX-License-Identifier: MIT + +import 'package:bro_abstract_manager/src/errors/platform_not_supported_by_manager_error.dart'; +import 'package:bro_abstract_manager/src/services/abs_with_life_cycle.dart'; +import 'package:bro_abstract_manager/src/types/wrong_platform_behavior.dart'; +import 'package:bro_platform_utility/bro_platform_utility.dart'; +import 'package:flutter/widgets.dart'; + +/// This is an abstract class to build a manager with a lifecycle. +/// +/// This is used to build managers for get it package. +abstract class AbsManagerBuilder { + /// Class constructor. + const AbsManagerBuilder(); + + /// {@template bro_abstract_manager.AbsManagerBuilder.create} + /// Call this method to create a new manager. + /// {@endtemplate} + Manager create(); + + /// {@template bro_abstract_manager.AbsManagerBuilder.build} + /// Build the manager and call the [AbsWithLifeCycle.initLifeCycle] method. + /// + /// If the [managerToInit] is not null, it will be used instead of creating a new manager. + /// The [currentPlatform] is used to check if the manager is supported on the current platform. + /// If null, we try to guess the current platform. + /// {@endtemplate} + @mustCallSuper + Future build({ + Manager? managerToInit, + PlatformType? currentPlatform, + }) async { + final isPlatformManaged = + _isManagerCanBeInitializedWithCurrentPlatform(currentPlatform: currentPlatform); + final manager = managerToInit ?? create(); + if (!isPlatformManaged) { + // The platform is not managed by the manager, so we don't init it. + return manager; + } + + await manager.initLifeCycle(); + return manager; + } + + /// {@template bro_abstract_manager.AbsManagerBuilder.getDependencies} + /// Get the dependencies of the manager. + /// + /// [Type] is the type of the dependencies and must be a subclass of [AbsWithLifeCycle]. + /// {@endtemplate} + @mustCallSuper + Iterable getDependencies(); + + /// {@template bro_abstract_manager.AbsManagerBuilder.getSupportedPlatforms} + /// Get the platforms supported by the manager. + /// + /// If the list contains [PlatformType.irrelevant], the manager is supported on all platforms. + /// + /// The behavior of the manager building on an unsupported platform is defined by the + /// [getWrongPlatformBehavior] method. This is used when we [build] the manager. + /// {@endtemplate} + List getSupportedPlatforms() => const [PlatformType.irrelevant]; + + /// {@template bro_abstract_manager.AbsManagerBuilder.getWrongPlatformBehavior} + /// Get the behavior of the manager building on an unsupported platform. + /// {@endtemplate} + WrongPlatformBehavior getWrongPlatformBehavior() => WrongPlatformBehavior.throwError; + + /// {@template bro_abstract_manager.AbsManagerBuilder.isPlatformSupported} + /// Check if the current platform is supported by the manager. + /// {@endtemplate} + bool isPlatformSupported({ + PlatformType? currentPlatform, + }) { + final supportedPlatforms = getSupportedPlatforms(); + return PlatformType.isCurrentSupported( + currentPlatform: currentPlatform, + supportedPlatforms: supportedPlatforms, + ); + } + + /// The method checks if the manager can be initialized with the current platform. + /// + /// If not, the [WrongPlatformBehavior] is used to determine the behavior of the manager building. + /// + /// The method throws an Error if the platform is not supported at all. + /// + /// The method returns false if the manager can be created but not initialized. This is used to + /// avoid calling the [AbsWithLifeCycle.initLifeCycle] method on an unsupported platform. + /// The method returns true if the manager can be created and initialized. + bool _isManagerCanBeInitializedWithCurrentPlatform({ + required PlatformType? currentPlatform, + }) { + final isCurrentlySupported = isPlatformSupported(currentPlatform: currentPlatform); + if (isCurrentlySupported) { + return true; + } + + final wrongPlatformBehavior = getWrongPlatformBehavior(); + switch (wrongPlatformBehavior) { + case WrongPlatformBehavior.throwError: + throw PlatformNotSupportedByManagerError( + currentPlatform: currentPlatform!, + supportedPlatforms: getSupportedPlatforms(), + ); + case WrongPlatformBehavior.doNotInitManager: + return false; + case WrongPlatformBehavior.continueProcess: + return true; + } + } +} diff --git a/bro_abstract_manager/lib/src/abs_with_life_cycle.dart b/bro_abstract_manager/lib/src/services/abs_with_life_cycle.dart similarity index 100% rename from bro_abstract_manager/lib/src/abs_with_life_cycle.dart rename to bro_abstract_manager/lib/src/services/abs_with_life_cycle.dart diff --git a/bro_abstract_manager/lib/src/types/wrong_platform_behavior.dart b/bro_abstract_manager/lib/src/types/wrong_platform_behavior.dart new file mode 100644 index 0000000..49ce4a3 --- /dev/null +++ b/bro_abstract_manager/lib/src/types/wrong_platform_behavior.dart @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: 2024 Benoit Rolandeau +// +// SPDX-License-Identifier: MIT + +/// The behavior to use when the platform is not supported by the manager. +enum WrongPlatformBehavior { + /// Throw an error when the platform is not supported by the manager. + throwError, + + /// Do not initialize the manager when the platform is not supported by the manager. + doNotInitManager, + + /// Continue the process when the platform is not supported by the manager, as if it's supported. + /// The manager will have to manage the case by itself. + continueProcess; +} diff --git a/bro_abstract_manager/pubspec.yaml b/bro_abstract_manager/pubspec.yaml index 847f3af..582196d 100644 --- a/bro_abstract_manager/pubspec.yaml +++ b/bro_abstract_manager/pubspec.yaml @@ -3,7 +3,7 @@ # SPDX-License-Identifier: MIT name: bro_abstract_manager -version: "1.0.1" +version: "1.1.0" description: This package contains abstract classes to work with managers. repository: https://github.com/borlnov/flutterbrolibs @@ -23,6 +23,8 @@ dependencies: flutter: sdk: flutter + bro_platform_utility: ^1.0.0 + dev_dependencies: flutter_test: sdk: flutter diff --git a/bro_abstract_manager/test/abs_manager_builder_test.dart b/bro_abstract_manager/test/abs_manager_builder_test.dart index 1fc4439..496eafb 100644 --- a/bro_abstract_manager/test/abs_manager_builder_test.dart +++ b/bro_abstract_manager/test/abs_manager_builder_test.dart @@ -2,15 +2,20 @@ // // SPDX-License-Identifier: MIT +import 'package:bro_abstract_manager/bro_abstract_manager.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'mock/services/a_manager.dart'; import 'mock/services/a_manager_builder.dart'; import 'mock/types/manager_status.dart'; /// Test the abstract manager builder. void main() { test("Test the abstract manager builder", () async { - final builder = AManagerBuilder(); + final builder = const AManagerBuilder( + supportedPlatforms: [PlatformType.irrelevant], + platformBehavior: WrongPlatformBehavior.throwError, + ); final manager = await builder.build(); expect(manager, isNotNull, reason: "The manager should not be null after build"); @@ -20,4 +25,109 @@ void main() { reason: "The manager should be initialized after build", ); }); + + test("Test reuse manager", () async { + final builder = const AManagerBuilder( + supportedPlatforms: [PlatformType.irrelevant], + platformBehavior: WrongPlatformBehavior.throwError, + ); + final tmpManager = AManager(); + final manager = await builder.build( + managerToInit: tmpManager, + ); + + expect( + manager, + tmpManager, + reason: "The manager got should be the same as the one passed in parameter", + ); + expect( + manager.status, + ManagerStatus.initialized, + reason: "The manager should be initialized after build", + ); + }); + + test("Test supported one platform", () async { + final builder = const AManagerBuilder( + supportedPlatforms: [PlatformType.android], + platformBehavior: WrongPlatformBehavior.throwError, + ); + final manager = await builder.build( + currentPlatform: PlatformType.android, + ); + + expect(manager, isNotNull, reason: "The manager should not be null after build"); + expect( + manager.status, + ManagerStatus.initialized, + reason: "The manager should be initialized after build", + ); + }); + + test("Test supported multiples platform", () async { + final builder = const AManagerBuilder( + supportedPlatforms: [PlatformType.android, PlatformType.iOS], + platformBehavior: WrongPlatformBehavior.throwError, + ); + final manager = await builder.build( + currentPlatform: PlatformType.android, + ); + + expect(manager, isNotNull, reason: "The manager should not be null after build"); + expect( + manager.status, + ManagerStatus.initialized, + reason: "The manager should be initialized after build", + ); + }); + + test("Test not supported platform, doesn't init", () async { + final builder = const AManagerBuilder( + supportedPlatforms: [PlatformType.iOS], + platformBehavior: WrongPlatformBehavior.doNotInitManager, + ); + final manager = await builder.build( + currentPlatform: PlatformType.android, + ); + + expect(manager, isNotNull, reason: "The manager should not be null after build"); + expect( + manager.status, + ManagerStatus.created, + reason: "The manager should be created after build", + ); + }); + + test("Test not supported platform, continue process", () async { + final builder = const AManagerBuilder( + supportedPlatforms: [PlatformType.iOS], + platformBehavior: WrongPlatformBehavior.continueProcess, + ); + final manager = await builder.build( + currentPlatform: PlatformType.android, + ); + + expect(manager, isNotNull, reason: "The manager should not be null after build"); + expect( + manager.status, + ManagerStatus.initialized, + reason: "The manager should be initialized after build", + ); + }); + + test("Test not supported platform, throw error", () async { + final builder = const AManagerBuilder( + supportedPlatforms: [PlatformType.iOS], + platformBehavior: WrongPlatformBehavior.throwError, + ); + + await expectLater( + builder.build( + currentPlatform: PlatformType.android, + ), + throwsA(isA()), + reason: "The build has thrown an error", + ); + }); } diff --git a/bro_abstract_manager/test/mock/services/a_manager_builder.dart b/bro_abstract_manager/test/mock/services/a_manager_builder.dart index 3baa77b..43725a9 100644 --- a/bro_abstract_manager/test/mock/services/a_manager_builder.dart +++ b/bro_abstract_manager/test/mock/services/a_manager_builder.dart @@ -8,10 +8,27 @@ import 'a_manager.dart'; /// This is the builder to create the [AManager]. class AManagerBuilder extends AbsManagerBuilder { - /// Create the [AManagerBuilder]. - AManagerBuilder() : super(AManager.new); + final WrongPlatformBehavior platformBehavior; + final List supportedPlatforms; + + const AManagerBuilder({ + required this.supportedPlatforms, + required this.platformBehavior, + }); + + /// {@macro bro_abstract_manager.AbsManagerBuilder.create} + @override + AManager create() => AManager(); /// {@macro bro_abstract_manager.AbsManagerBuilder.getDependencies} @override Iterable getDependencies() => []; + + /// {@macro bro_abstract_manager.AbsManagerBuilder.getWrongPlatformBehavior} + @override + WrongPlatformBehavior getWrongPlatformBehavior() => platformBehavior; + + /// {@macro bro_abstract_manager.AbsManagerBuilder.getSupportedPlatforms} + @override + List getSupportedPlatforms() => supportedPlatforms; } diff --git a/bro_config_manager/lib/src/services/abs_config_manager.dart b/bro_config_manager/lib/src/services/abs_config_manager.dart index 3534abc..78006c1 100644 --- a/bro_config_manager/lib/src/services/abs_config_manager.dart +++ b/bro_config_manager/lib/src/services/abs_config_manager.dart @@ -14,7 +14,7 @@ import 'package:flutter/foundation.dart'; /// The abstract builder to create a [AbstractConfigManager]. abstract class AbsConfigBuilder extends AbsManagerBuilder { /// Class constructor. - const AbsConfigBuilder(super.managerFactory); + const AbsConfigBuilder(); } /// The abstract class to manage the configuration of the application. @@ -68,5 +68,6 @@ abstract class AbstractConfigManager extends AbsWithLifeCycle with MixinManagerW /// This method returns the model to initialize the ConfigManager. /// {@endtemplate} @protected - Future getInitConfigManagerModel(); + Future getInitConfigManagerModel() async => + const InitConfigManagerModel(); } diff --git a/bro_config_manager/pubspec.yaml b/bro_config_manager/pubspec.yaml index b53cfa2..1596735 100644 --- a/bro_config_manager/pubspec.yaml +++ b/bro_config_manager/pubspec.yaml @@ -29,11 +29,11 @@ dependencies: bro_abstract_logger: ^1.1.0 - bro_abstract_manager: ^1.0.1 + bro_abstract_manager: ^1.1.0 bro_file_utility: ^1.0.0 - bro_global_manager: ^1.0.0 + bro_global_manager: ^1.1.0 bro_yaml_utility: ^1.0.0 diff --git a/bro_config_manager/test/mock/services/a_config_manager.dart b/bro_config_manager/test/mock/services/a_config_manager.dart index b7031c9..da54f21 100644 --- a/bro_config_manager/test/mock/services/a_config_manager.dart +++ b/bro_config_manager/test/mock/services/a_config_manager.dart @@ -8,16 +8,29 @@ import '../mixins/mixin_for_tests_config.dart'; /// A builder for the [AConfigManager] used for the tests. class AConfigBuilder extends AbsConfigBuilder { + /// This is the relative folder path to find the configuration files. + final String configFolderPath; + + /// The environment type to use. + final ConfigEnvironmentType envType; + + /// The constant environment values passed when building the app. + final Map constEnvsValues; + /// The constructor for the [AConfigBuilder]. AConfigBuilder({ - required String configFolderPath, - required ConfigEnvironmentType envType, - required Map constEnvsValues, - }) : super(() => AConfigManager( - configFolderPath: configFolderPath, - envType: envType, - constEnvsValues: constEnvsValues, - )); + required this.configFolderPath, + required this.envType, + required this.constEnvsValues, + }) : super(); + + /// {@macro bro_abstract_manager.AbsManagerBuilder.create} + @override + AConfigManager create() => AConfigManager( + configFolderPath: configFolderPath, + envType: envType, + constEnvsValues: constEnvsValues, + ); /// {@macro bro_abstract_manager.AbsManagerBuilder.getDependencies} @override diff --git a/bro_firebase_core/.gitignore b/bro_firebase_core/.gitignore new file mode 100644 index 0000000..77f9caa --- /dev/null +++ b/bro_firebase_core/.gitignore @@ -0,0 +1,35 @@ +# SPDX-FileCopyrightText: 2025 Benoit Rolandeau +# +# SPDX-License-Identifier: MIT + +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +build/ diff --git a/bro_firebase_core/.metadata b/bro_firebase_core/.metadata new file mode 100644 index 0000000..2a15a9b --- /dev/null +++ b/bro_firebase_core/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "17025dd88227cd9532c33fa78f5250d548d87e9a" + channel: "stable" + +project_type: package diff --git a/bro_firebase_core/.metadata.license b/bro_firebase_core/.metadata.license new file mode 100644 index 0000000..ff5c7fc --- /dev/null +++ b/bro_firebase_core/.metadata.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 Benoit Rolandeau + +SPDX-License-Identifier: MIT diff --git a/bro_firebase_core/CHANGELOG.md b/bro_firebase_core/CHANGELOG.md new file mode 100644 index 0000000..13cabb8 --- /dev/null +++ b/bro_firebase_core/CHANGELOG.md @@ -0,0 +1,12 @@ + + + + +## 1.0.0 + +- Initial release. diff --git a/bro_firebase_core/LICENSE b/bro_firebase_core/LICENSE new file mode 100644 index 0000000..d817195 --- /dev/null +++ b/bro_firebase_core/LICENSE @@ -0,0 +1,18 @@ +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/bro_firebase_core/README.md b/bro_firebase_core/README.md new file mode 100644 index 0000000..ac2ad77 --- /dev/null +++ b/bro_firebase_core/README.md @@ -0,0 +1,43 @@ + + +# Bro firebase core + +## Table of contents + +- [Table of contents](#table-of-contents) +- [Introduction](#introduction) +- [Quick start](#quick-start) + - [Install firebase tools](#install-firebase-tools) + - [Configure the firebase project](#configure-the-firebase-project) + +## Introduction + +This package imports the [firebase_core](https://pub.dev/packages/firebase_core) package to the +manager system (_see [bro_abstract_manager](https://pub.dev/packages/bro_abstract_manager)_). + +## Quick start + +### Install firebase tools + +This application uses firebase, so you need to install the firebase tools: + +- `firebase` +- `flutterfire_cli` + +You can follow the doc here: +[install tools](https://firebase.google.com/docs/flutter/setup?hl=fr&platform=android#install-cli-tools). + +### Configure the firebase project + +After having installed the firebase tools and login to your account, you need to configure the +firebase project. + +You have to call the following command: + +```bash +flutterfire configure +``` diff --git a/bro_firebase_core/analysis_options.yaml b/bro_firebase_core/analysis_options.yaml new file mode 100644 index 0000000..062cde8 --- /dev/null +++ b/bro_firebase_core/analysis_options.yaml @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: 2025 Benoit Rolandeau +# +# SPDX-License-Identifier: MIT + +include: ../analysis_options.yaml diff --git a/bro_firebase_core/lib/bro_firebase_core.dart b/bro_firebase_core/lib/bro_firebase_core.dart new file mode 100644 index 0000000..f744c2a --- /dev/null +++ b/bro_firebase_core/lib/bro_firebase_core.dart @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: 2025 Benoit Rolandeau +// +// SPDX-License-Identifier: MIT + +library; + +export 'package:firebase_core/firebase_core.dart' show FirebaseOptions; + +export 'src/models/init_firebase_config.dart'; +export 'src/services/abs_firebase_manager.dart'; +export 'src/services/abs_firebase_service.dart'; diff --git a/bro_firebase_core/lib/src/models/init_firebase_config.dart b/bro_firebase_core/lib/src/models/init_firebase_config.dart new file mode 100644 index 0000000..d867d8e --- /dev/null +++ b/bro_firebase_core/lib/src/models/init_firebase_config.dart @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2025 Benoit Rolandeau +// +// SPDX-License-Identifier: MIT + +import 'package:bro_firebase_core/src/services/abs_firebase_service.dart'; +import 'package:equatable/equatable.dart'; +import 'package:firebase_core/firebase_core.dart'; + +/// This is the configuration used to initialize Firebase. +class InitFirebaseConfig extends Equatable { + /// The [FirebaseOptions] used to initialize Firebase. + final FirebaseOptions firebaseOptions; + + /// The list of [AbsFirebaseService] to initialize. + final List services; + + /// Class constructor. + const InitFirebaseConfig({ + required this.firebaseOptions, + this.services = const [], + }); + + /// {@macro bro_abstract_manager.AbsManagerBuilder.getDependencies} + @override + List get props => [firebaseOptions, services]; +} diff --git a/bro_firebase_core/lib/src/services/abs_firebase_manager.dart b/bro_firebase_core/lib/src/services/abs_firebase_manager.dart new file mode 100644 index 0000000..72d319f --- /dev/null +++ b/bro_firebase_core/lib/src/services/abs_firebase_manager.dart @@ -0,0 +1,69 @@ +// SPDX-FileCopyrightText: 2025 Benoit Rolandeau +// +// SPDX-License-Identifier: MIT + +import 'package:bro_firebase_core/src/models/init_firebase_config.dart'; +import 'package:bro_firebase_core/src/services/abs_firebase_service.dart'; +import 'package:bro_global_manager/bro_global_manager.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter/widgets.dart'; + +/// This is the abstract builder for the [AbsFirebaseManager]. +abstract class AbsFirebaseBuilder extends AbsManagerBuilder { + /// Class constructor + const AbsFirebaseBuilder(); +} + +/// This is the abstract manager for Firebase. +/// +/// This class is used to initialize Firebase and its services. The services are initialized in +/// the [initLifeCycle] method and disposed in the [disposeLifeCycle] method. +/// +/// The managed [AbsFirebaseService] are got from the [InitFirebaseConfig] returned by the +/// [getFirebaseOptions] method. +abstract class AbsFirebaseManager extends AbsWithLifeCycle { + /// The list of the [AbsFirebaseService] managed by this manager. + final List _services; + + /// The [FirebaseApp] instance used by this manager. + late final FirebaseApp _app; + + /// Class constructor. + AbsFirebaseManager() : _services = []; + + /// {@macro bro_abstract_manager.AbsWithLifeCycle.initLifeCycle} + @override + Future initLifeCycle() async { + await super.initLifeCycle(); + final config = await getFirebaseOptions(); + + _services.addAll(config.services); + + _app = await Firebase.initializeApp( + options: config.firebaseOptions, + ); + + await Future.wait(_services.map((service) => service.initLifeCycle())); + } + + /// {@macro bro_abstract_manager.AbsWithLifeCycle.initAfterViewBuilt} + @override + Future initAfterViewBuilt(BuildContext context) async { + await super.initAfterViewBuilt(context); + await Future.wait(_services.map((service) => service.initAfterViewBuilt(context))); + } + + /// {@template abs_firebase_manager.AbsFirebaseManager.getFirebaseOptions} + /// Get the [InitFirebaseConfig] config to init the manager and the linked services. + /// {@endtemplate} + @protected + Future getFirebaseOptions(); + + /// {@macro bro_abstract_manager.AbsWithLifeCycle.disposeLifeCycle} + @override + Future disposeLifeCycle() async { + await Future.wait(_services.map((service) => service.disposeLifeCycle())); + await _app.delete(); + await super.disposeLifeCycle(); + } +} diff --git a/bro_firebase_core/lib/src/services/abs_firebase_service.dart b/bro_firebase_core/lib/src/services/abs_firebase_service.dart new file mode 100644 index 0000000..3ae5afe --- /dev/null +++ b/bro_firebase_core/lib/src/services/abs_firebase_service.dart @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: 2025 Benoit Rolandeau +// +// SPDX-License-Identifier: MIT + +import 'package:bro_global_manager/bro_global_manager.dart'; + +/// An abstract class for the Firebase services. +abstract class AbsFirebaseService extends AbsWithLifeCycle { + /// Class constructor. + const AbsFirebaseService(); +} diff --git a/bro_firebase_core/mono_pkg.yaml b/bro_firebase_core/mono_pkg.yaml new file mode 100644 index 0000000..f1aa13e --- /dev/null +++ b/bro_firebase_core/mono_pkg.yaml @@ -0,0 +1,16 @@ +# SPDX-FileCopyrightText: 2025 Benoit Rolandeau +# +# SPDX-License-Identifier: MIT + +sdk: + - stable + +stages: + - analyze: + - group: + - analyze: --fatal-infos . + - command: + - dart pub global activate pana + # TODO(borlnov): Add `--exit-code-threshold 20` when the issue: + # https://github.com/dart-lang/pana/issues/1020, is fixed. + - pana --line-length 100 . diff --git a/bro_firebase_core/pubspec.yaml b/bro_firebase_core/pubspec.yaml new file mode 100644 index 0000000..85f058e --- /dev/null +++ b/bro_firebase_core/pubspec.yaml @@ -0,0 +1,34 @@ +# SPDX-FileCopyrightText: 2025 Benoit Rolandeau +# +# SPDX-License-Identifier: MIT + +name: bro_firebase_core +version: "1.0.0" + +description: Wraps the Firebase Core package to use it with the managers system. +repository: https://github.com/borlnov/flutterbrolibs +topics: + - firebase + - core + - brolibs + +environment: + sdk: ^3.6.0 + flutter: ">=1.17.0" + +resolution: workspace + +dependencies: + flutter: + sdk: flutter + + equatable: ^2.0.7 + + firebase_core: ^3.9.0 + + bro_global_manager: ^1.1.0 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^5.0.0 diff --git a/bro_firebase_crashlytics/.gitignore b/bro_firebase_crashlytics/.gitignore new file mode 100644 index 0000000..77f9caa --- /dev/null +++ b/bro_firebase_crashlytics/.gitignore @@ -0,0 +1,35 @@ +# SPDX-FileCopyrightText: 2025 Benoit Rolandeau +# +# SPDX-License-Identifier: MIT + +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +build/ diff --git a/bro_firebase_crashlytics/.metadata b/bro_firebase_crashlytics/.metadata new file mode 100644 index 0000000..2a15a9b --- /dev/null +++ b/bro_firebase_crashlytics/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "17025dd88227cd9532c33fa78f5250d548d87e9a" + channel: "stable" + +project_type: package diff --git a/bro_firebase_crashlytics/.metadata.license b/bro_firebase_crashlytics/.metadata.license new file mode 100644 index 0000000..ff5c7fc --- /dev/null +++ b/bro_firebase_crashlytics/.metadata.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 Benoit Rolandeau + +SPDX-License-Identifier: MIT diff --git a/bro_firebase_crashlytics/CHANGELOG.md b/bro_firebase_crashlytics/CHANGELOG.md new file mode 100644 index 0000000..13cabb8 --- /dev/null +++ b/bro_firebase_crashlytics/CHANGELOG.md @@ -0,0 +1,12 @@ + + + + +## 1.0.0 + +- Initial release. diff --git a/bro_firebase_crashlytics/LICENSE b/bro_firebase_crashlytics/LICENSE new file mode 100644 index 0000000..d817195 --- /dev/null +++ b/bro_firebase_crashlytics/LICENSE @@ -0,0 +1,18 @@ +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/bro_firebase_crashlytics/README.md b/bro_firebase_crashlytics/README.md new file mode 100644 index 0000000..fe82e70 --- /dev/null +++ b/bro_firebase_crashlytics/README.md @@ -0,0 +1,27 @@ + + +# Bro firebase crashlytics + +## Table of contents + +- [Table of contents](#table-of-contents) +- [Introduction](#introduction) +- [Configuration](#configuration) + +## Introduction + +This package imports the [firebase_crashlytics](https://pub.dev/packages/firebase_crashlytics) +package to the manager system +(_see [bro_abstract_manager](https://pub.dev/packages/bro_abstract_manager)_). + +## Configuration + +If you use the [CrashlyticsLoggerManager](lib/src/services/crashlytics_logger_manager.dart) class, +you have to add the [MixinCrashlyticsConfigs](lib/src/mixins/mixin_crashlytics_configs.dart) to your +app config manager. + +Read the mixin, to see what you have to set in your config files. diff --git a/bro_firebase_crashlytics/analysis_options.yaml b/bro_firebase_crashlytics/analysis_options.yaml new file mode 100644 index 0000000..062cde8 --- /dev/null +++ b/bro_firebase_crashlytics/analysis_options.yaml @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: 2025 Benoit Rolandeau +# +# SPDX-License-Identifier: MIT + +include: ../analysis_options.yaml diff --git a/bro_firebase_crashlytics/lib/bro_firebase_crashlytics.dart b/bro_firebase_crashlytics/lib/bro_firebase_crashlytics.dart new file mode 100644 index 0000000..8e618fc --- /dev/null +++ b/bro_firebase_crashlytics/lib/bro_firebase_crashlytics.dart @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: 2025 Benoit Rolandeau +// +// SPDX-License-Identifier: MIT + +library; + +export 'src/mixins/mixin_crashlytics_configs.dart'; +export 'src/services/crashlytics_logger_manager.dart'; diff --git a/bro_firebase_crashlytics/lib/src/helpers/crashlytics_logger.dart b/bro_firebase_crashlytics/lib/src/helpers/crashlytics_logger.dart new file mode 100644 index 0000000..54e02f7 --- /dev/null +++ b/bro_firebase_crashlytics/lib/src/helpers/crashlytics_logger.dart @@ -0,0 +1,174 @@ +// SPDX-FileCopyrightText: 2025 Benoit Rolandeau +// +// SPDX-License-Identifier: MIT + +import 'dart:async'; + +import 'package:bro_abstract_logger/bro_abstract_logger.dart'; +import 'package:firebase_crashlytics/firebase_crashlytics.dart'; + +/// This class is used as external logger to send logs to Firebase Crashlytics. +class CrashlyticsLogger with MixinExternalLogger { + /// True if Crashlytics is enabled and the logs should be sent to Firebase Crashlytics. + final bool crashlyticsEnabled; + + /// The number of last logs to send to Firebase Crashlytics. + /// If null or if the number is less than or equal to 0, no logs will be sent to Firebase + /// Crashlytics. + /// + /// This works as a circular buffer. The first logs are removed when the buffer is full. + final int? includeLastLogsNb; + + /// The minimum level of logs to send to Firebase Crashlytics. + final LogsLevel includeLastLogsMinLevel; + + /// The last logs to send to Firebase Crashlytics. + final List _lastLogs; + + /// Class constructor + CrashlyticsLogger({ + required this.crashlyticsEnabled, + required this.includeLastLogsNb, + required this.includeLastLogsMinLevel, + }) : _lastLogs = []; + + /// {@macro bro_abstract_logger.MixinExternalLogger.log} + /// If the log is an error or fatal, we send it to Firebase Crashlytics. Otherwise, we add it to + /// the last logs + @override + void log( + LogsLevel level, + String message, { + List categories = const [], + }) { + if (!crashlyticsEnabled) { + return; + } + + if (_isAnError(level)) { + // We fire the error + unawaited(_fireError( + exception: message, + level: level, + categories: categories, + )); + return; + } + + _addLastLogsIfNeeded( + level: level, + message: message, + categories: categories, + ); + } + + /// {@macro bro_abstract_logger.MixinExternalLogger.logErrorWithException} + /// + /// We send the log to Firebase Crashlytics. + @override + void logErrorWithException( + // We use dynamic here to be able to log any type of exception + // ignore: avoid_annotating_with_dynamic + dynamic exception, { + StackTrace? stackTrace, + bool isFatal = false, + List categories = const [], + }) { + if (!crashlyticsEnabled) { + return; + } + + unawaited(_fireError( + exception: exception, + level: isFatal ? LogsLevel.fatal : LogsLevel.error, + stackTrace: stackTrace, + categories: categories, + )); + } + + /// Test if the [level] is an error or fatal log, that should be sent to Firebase Crashlytics. + bool _isAnError(LogsLevel level) => switch (level) { + LogsLevel.trace || + LogsLevel.debug || + LogsLevel.info || + LogsLevel.warn || + LogsLevel.none => + false, + LogsLevel.error || LogsLevel.fatal => true, + }; + + /// Add the log to the last logs if needed. + void _addLastLogsIfNeeded({ + required LogsLevel level, + // We use dynamic here to be able to log any type of message + // ignore: avoid_annotating_with_dynamic + dynamic message, + List categories = const [], + StackTrace? stackTrace, + // We use dynamic here to be able to log any type of exception + // ignore: avoid_annotating_with_dynamic + dynamic exception, + }) { + if (includeLastLogsNb == null || includeLastLogsNb! <= 0) { + // We don't need to keep the last logs. + return; + } + + if (level.index < includeLastLogsMinLevel.index) { + // We don't want to keep this log because its level is too low. + return; + } + + // We remove the first logs if the list is too long to accept a new log. + final rangeToRemove = _lastLogs.length - includeLastLogsNb! + 1; + if (rangeToRemove > 0) { + _lastLogs.removeRange(0, rangeToRemove); + } + + // We add the new log. + // If there are multiple lines we join them with a `\n` because the case is rare and we don't + // want to have partial information in the logs sent to Crashlytics. + _lastLogs.add(LogFormatUtility.formatLogMessages( + message: message, + categories: categories, + level: level, + stackTrace: stackTrace, + exception: exception, + ).join("\n")); + } + + /// Fire an error to Firebase Crashlytics. + /// + /// Before sending the error, we send the last logs to Firebase Crashlytics. + Future _fireError({ + // We use dynamic here to be able to log any type of exception + // ignore: avoid_annotating_with_dynamic + required dynamic exception, + required LogsLevel level, + StackTrace? stackTrace, + List categories = const [], + }) async { + // Because log method is asynchronous, we can't be sure that new log can't be added before the + // error is fired. That's why we make a copy of the current logs. + final currentLogs = List.from(_lastLogs, growable: false); + // We clear the logs to avoid having the same logs in the next error. + _lastLogs.clear(); + for (final log in currentLogs) { + await FirebaseCrashlytics.instance.log(log); + } + + await FirebaseCrashlytics.instance.recordError( + exception, + stackTrace, + fatal: level == LogsLevel.fatal, + information: [ + "Level: ${level.name.toLowerCase()}", + if (categories.isNotEmpty) "Categories: ${LogFormatUtility.formatCategories(categories)}", + ], + ); + } + + /// {@macro bro_abstract_logger.MixinExternalLogger.dispose} + @override + Future dispose() async {} +} diff --git a/bro_firebase_crashlytics/lib/src/mixins/mixin_crashlytics_configs.dart b/bro_firebase_crashlytics/lib/src/mixins/mixin_crashlytics_configs.dart new file mode 100644 index 0000000..082e6c4 --- /dev/null +++ b/bro_firebase_crashlytics/lib/src/mixins/mixin_crashlytics_configs.dart @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: 2025 Benoit Rolandeau +// +// SPDX-License-Identifier: MIT + +import 'package:bro_abstract_logger/bro_abstract_logger.dart'; +import 'package:bro_config_manager/bro_config_manager.dart'; + +/// Contains the config variables for the crashlytics manager. +mixin MixinCrashlyticsConfigs on AbstractConfigManager { + /// Says if the crashlytics is enabled or not. + final crashlyticsEnabled = SimpleConfigVar.jsonPathList( + jsonPathList: ["firebase", "crashlytics", "enabled"], + defaultValue: false, + ); + + /// The number of last logs to include in the crash report. + final includeLastLogsNb = SimpleConfigVar.jsonPathList( + jsonPathList: ["firebase", "crashlytics", "includeLastLogs", "logsNb"], + ); + + /// The minimum level of logs to include in the crash report. + final includeLastLogsMinLevel = ConfigVar.jsonPathList( + jsonPathList: ["firebase", "crashlytics", "includeLastLogs", "minLevel"], + converter: LogsLevel.parseFromString, + defaultValue: LogsLevel.warn, + ); +} diff --git a/bro_firebase_crashlytics/lib/src/services/crashlytics_logger_manager.dart b/bro_firebase_crashlytics/lib/src/services/crashlytics_logger_manager.dart new file mode 100644 index 0000000..4c77f7e --- /dev/null +++ b/bro_firebase_crashlytics/lib/src/services/crashlytics_logger_manager.dart @@ -0,0 +1,160 @@ +// SPDX-FileCopyrightText: 2025 Benoit Rolandeau +// +// SPDX-License-Identifier: MIT + +import 'package:bro_abstract_logger/bro_abstract_logger.dart'; +import 'package:bro_config_manager/bro_config_manager.dart'; +import 'package:bro_firebase_crashlytics/src/helpers/crashlytics_logger.dart'; +import 'package:bro_firebase_crashlytics/src/mixins/mixin_crashlytics_configs.dart'; +import 'package:bro_global_manager/bro_global_manager.dart'; +import 'package:firebase_crashlytics/firebase_crashlytics.dart'; + +/// This is the builder to create the [CrashlyticsLoggerManager]. +class CrashlyticsBuilder + extends AbsLoggerBuilder { + /// Create the [CrashlyticsBuilder]. + const CrashlyticsBuilder() : super(); + + /// {@macro bro_abstract_manager.AbsManagerBuilder.create} + @override + CrashlyticsLoggerManager create() => CrashlyticsLoggerManager( + configManagerGetter: globalGetManager, + isPlatformSupported: isPlatformSupported(), + ); + + /// {@macro bro_abstract_manager.AbsManagerBuilder.getSupportedPlatforms} + @override + List getSupportedPlatforms() => const [PlatformType.android, PlatformType.iOS]; + + /// {@macro bro_abstract_manager.AbsManagerBuilder.getWrongPlatformBehavior} + @override + WrongPlatformBehavior getWrongPlatformBehavior() => WrongPlatformBehavior.continueProcess; + + /// {@macro bro_abstract_manager.AbsManagerBuilder.getDependencies} + @override + Iterable getDependencies() => [C]; +} + +/// This is the logger manager used to log with Firebase Crashlytics. +/// +/// This manager is used to log with Firebase Crashlytics. It will only be used if the platform is +/// supported and if the [_crashlyticsEnabled] is true. +/// +/// To use it with your favorite logger manager, you should use both with the +/// [AbstractMultiLoggerManager]. +/// +/// To use this manager, you have to add the [MixinCrashlyticsConfigs] to your implementation of +/// the [AbstractConfigManager]. +class CrashlyticsLoggerManager extends AbstractLoggerManager { + /// This is the getter to get the [MixinCrashlyticsConfigs]. + final MixinCrashlyticsConfigs Function() configManagerGetter; + + /// This is the flag to know if the current platform is supported, or not. If not, the manager is + /// initiazed but crashlytics is disabled (like if [_crashlyticsEnabled] is equal to false). + final bool isPlatformSupported; + + /// This is the flag to know if the crashlytics is enabled or not. + late final bool _crashlyticsEnabled; + + /// This is used to know if the automatic sending of the crashlytics is enabled or not. + /// + /// If true, the crashlytics will be sent automatically at the application next start, otherwise, + /// you have to call the [forceReportsSending] method to send the crashlytics manually. + late final bool _autoCrashlyticsSent; + + /// This is the Firebase Crashlytics instance. + late final FirebaseCrashlytics _crashlytics; + + /// Class constructor + CrashlyticsLoggerManager({ + required this.configManagerGetter, + required this.isPlatformSupported, + }) : super(); + + /// {@macro bro_abstract_manager.AbsWithLifeCycle.initLifeCycle} + @override + Future initLifeCycle() async { + final configManager = configManagerGetter(); + + _crashlytics = FirebaseCrashlytics.instance; + if (isPlatformSupported) { + _crashlyticsEnabled = configManager.crashlyticsEnabled.load(); + _autoCrashlyticsSent = _crashlytics.isCrashlyticsCollectionEnabled; + } else { + _crashlyticsEnabled = false; + _autoCrashlyticsSent = false; + } + await super.initLifeCycle(); + } + + /// This method updates the auto crashlytics sent flag. + /// + /// If true, the crashlytics will be sent automatically at the application next start, otherwise, + /// you have to call the [forceReportsSending] method to send the crashlytics manually. + /// + /// If [_crashlyticsEnabled] is false, no logs will be sent to Firebase Crashlytics. + /// + /// If the current platform is not supported, the method does nothing. + Future updateAutoCrashlyticsSent({ + required bool value, + }) async { + if (!isPlatformSupported || _autoCrashlyticsSent == value) { + return; + } + + await _crashlytics.setCrashlyticsCollectionEnabled(value); + _autoCrashlyticsSent = value; + } + + /// This method forces the sending of the crashlytics. + /// + /// This method is only used if the [_autoCrashlyticsSent] is false. If it's true, the crashlytics + /// will be sent automatically at the application next start. + /// + /// If [_crashlyticsEnabled] is false, but there are logs saved in crashlytics, they will be sent + /// to Firebase Crashlytics. + /// + /// If the current platform is not supported, the method does nothing. + Future forceReportsSending() async { + if (!isPlatformSupported || _autoCrashlyticsSent) { + // This not relevant to force reports sending if auto sent is enabled + // Or if the platform is not supported + return; + } + + final isThereUnsentReport = await _crashlytics.checkForUnsentReports(); + if (!isThereUnsentReport) { + // Nothing to send + return; + } + + return FirebaseCrashlytics.instance.sendUnsentReports(); + } + + /// This method sets an user identifier. + /// + /// BE CAREFUL: this will identify the user in the Firebase Crashlytics console; therefore, you + /// have to get the user permission to do that. + /// + /// If [_crashlyticsEnabled] is false or if the current platform is not supported, the method + /// does nothing. + Future setUserIdentifier(String identifier) async { + if (!isPlatformSupported || !_crashlyticsEnabled) { + return; + } + + return _crashlytics.setUserIdentifier(identifier); + } + + /// {@macro bro_abstract_logger.AbstractLoggerManager.getExternalLogger} + @override + Future getExternalLogger() async { + final configManager = configManagerGetter(); + + return CrashlyticsLogger( + crashlyticsEnabled: _crashlyticsEnabled, + includeLastLogsNb: configManager.includeLastLogsNb.tryToLoad(), + includeLastLogsMinLevel: configManager.includeLastLogsMinLevel.load(), + ); + } +} diff --git a/bro_firebase_crashlytics/mono_pkg.yaml b/bro_firebase_crashlytics/mono_pkg.yaml new file mode 100644 index 0000000..f1aa13e --- /dev/null +++ b/bro_firebase_crashlytics/mono_pkg.yaml @@ -0,0 +1,16 @@ +# SPDX-FileCopyrightText: 2025 Benoit Rolandeau +# +# SPDX-License-Identifier: MIT + +sdk: + - stable + +stages: + - analyze: + - group: + - analyze: --fatal-infos . + - command: + - dart pub global activate pana + # TODO(borlnov): Add `--exit-code-threshold 20` when the issue: + # https://github.com/dart-lang/pana/issues/1020, is fixed. + - pana --line-length 100 . diff --git a/bro_firebase_crashlytics/pubspec.yaml b/bro_firebase_crashlytics/pubspec.yaml new file mode 100644 index 0000000..52d026e --- /dev/null +++ b/bro_firebase_crashlytics/pubspec.yaml @@ -0,0 +1,37 @@ +# SPDX-FileCopyrightText: 2025 Benoit Rolandeau +# +# SPDX-License-Identifier: MIT + +name: bro_firebase_crashlytics +version: "1.0.0" + +description: Wraps the Firebase Crashlytics package to use it with the managers and loggers systems. +repository: https://github.com/borlnov/flutterbrolibs +topics: + - firebase + - crashlytics + - logger + - brolibs + +environment: + sdk: ^3.6.0 + flutter: ">=1.17.0" + +resolution: workspace + +dependencies: + flutter: + sdk: flutter + + firebase_crashlytics: ^4.2.0 + + bro_abstract_logger: ^1.1.0 + + bro_config_manager: ^1.1.0 + + bro_global_manager: ^1.1.0 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^5.0.0 diff --git a/bro_global_manager/lib/bro_global_manager.dart b/bro_global_manager/lib/bro_global_manager.dart index 4f3d7b1..4f8525b 100644 --- a/bro_global_manager/lib/bro_global_manager.dart +++ b/bro_global_manager/lib/bro_global_manager.dart @@ -4,8 +4,7 @@ library; -export 'package:bro_abstract_manager/bro_abstract_manager.dart' - show AbsManagerBuilder, AbsWithLifeCycle; +export 'package:bro_abstract_manager/bro_abstract_manager.dart'; export 'src/mixins/mixin_manager_with_logger.dart'; export 'src/models/global_manager_not_created_error.dart'; diff --git a/bro_global_manager/lib/src/services/abs_global_manager.dart b/bro_global_manager/lib/src/services/abs_global_manager.dart index 36cc4ea..2e47906 100644 --- a/bro_global_manager/lib/src/services/abs_global_manager.dart +++ b/bro_global_manager/lib/src/services/abs_global_manager.dart @@ -74,6 +74,9 @@ abstract class AbsGlobalManager extends AbsWithLifeCycle { /// Get the main logger helper of the application. LoggerHelper get appLoggerHelper => _loggerManager.loggerHelper; + /// The current platform of the application. + late final PlatformType currentPlatform; + /// Class constructor. AbsGlobalManager() : _currentStatus = GlobalManagerStatus.created, @@ -89,6 +92,8 @@ abstract class AbsGlobalManager extends AbsWithLifeCycle { return; } + currentPlatform = PlatformType.guessCurrentPlatform(); + final builders = _RegistrationBuilders(); registerManagers(>(B builder) => _registerManagerWithBuilder( @@ -101,6 +106,7 @@ abstract class AbsGlobalManager extends AbsWithLifeCycle { await _initManagers(builders); _currentStatus = GlobalManagerStatus.initialized; + appLoggerHelper.info("Global manager initialized."); } /// Initialize the lifecycle, after the first is built, of all the managers of the global manager. @@ -152,12 +158,15 @@ abstract class AbsGlobalManager extends AbsWithLifeCycle { }) { M? manager; if (builder is AbsLoggerBuilder) { - manager = builder.managerFactory(); + manager = builder.create(); _loggerManager = manager as AbstractLoggerManager; } builders[builder] = () async { - final tmpManager = await builder.build(managerToInit: manager); + final tmpManager = await builder.build( + managerToInit: manager, + currentPlatform: currentPlatform, + ); _managers[M] = tmpManager; }; } diff --git a/bro_global_manager/pubspec.yaml b/bro_global_manager/pubspec.yaml index 0d83a90..490b78f 100644 --- a/bro_global_manager/pubspec.yaml +++ b/bro_global_manager/pubspec.yaml @@ -23,10 +23,12 @@ dependencies: flutter: sdk: flutter - bro_abstract_manager: ^1.0.1 + bro_abstract_manager: ^1.1.0 bro_abstract_logger: ^1.1.0 + bro_platform_utility: ^1.0.0 + dev_dependencies: flutter_test: sdk: flutter diff --git a/bro_global_manager/test/mock/services/a_manager.dart b/bro_global_manager/test/mock/services/a_manager.dart index 147c9d9..93bb31d 100644 --- a/bro_global_manager/test/mock/services/a_manager.dart +++ b/bro_global_manager/test/mock/services/a_manager.dart @@ -10,7 +10,11 @@ import 'b_manager.dart'; /// Build a new [AManager]. class AManagerBuilder extends AbsManagerBuilder { /// Create a new [AManagerBuilder]. - const AManagerBuilder() : super(AManager.new); + const AManagerBuilder() : super(); + + /// {@macro bro_abstract_manager.AbsManagerBuilder.create} + @override + AManager create() => AManager(); /// {@macro bro_abstract_manager.AbsManagerBuilder.getDependencies} @override diff --git a/bro_global_manager/test/mock/services/b_manager.dart b/bro_global_manager/test/mock/services/b_manager.dart index 6cdb53e..fa603df 100644 --- a/bro_global_manager/test/mock/services/b_manager.dart +++ b/bro_global_manager/test/mock/services/b_manager.dart @@ -9,7 +9,11 @@ import '../mixins/mixin_life_cycle_status.dart'; /// Build a new [BManager]. class BManagerBuilder extends AbsManagerBuilder { /// Create a new [BManagerBuilder]. - const BManagerBuilder() : super(BManager.new); + const BManagerBuilder() : super(); + + /// {@macro bro_abstract_manager.AbsManagerBuilder.create} + @override + BManager create() => BManager(); /// {@macro bro_abstract_manager.AbsManagerBuilder.getDependencies} @override diff --git a/bro_global_manager/test/mock/services/c_logger_manager.dart b/bro_global_manager/test/mock/services/c_logger_manager.dart index be67061..ed2f290 100644 --- a/bro_global_manager/test/mock/services/c_logger_manager.dart +++ b/bro_global_manager/test/mock/services/c_logger_manager.dart @@ -11,7 +11,11 @@ import 'b_manager.dart'; /// This is the builder to create the [CLoggerManager]. class CLoggerBuilder extends AbsLoggerBuilder { /// Create the [CLoggerBuilder]. - const CLoggerBuilder() : super(CLoggerManager.new); + const CLoggerBuilder() : super(); + + /// {@macro bro_abstract_manager.AbsManagerBuilder.create} + @override + CLoggerManager create() => CLoggerManager(); /// {@macro bro_abstract_manager.AbsManagerBuilder.getDependencies} @override diff --git a/bro_global_manager/test/mock/services/d_manager.dart b/bro_global_manager/test/mock/services/d_manager.dart index eba8e77..e724a35 100644 --- a/bro_global_manager/test/mock/services/d_manager.dart +++ b/bro_global_manager/test/mock/services/d_manager.dart @@ -10,7 +10,11 @@ import 'e_manager.dart'; /// Build a new [DManager]. class DManagerBuilder extends AbsManagerBuilder { /// Create a new [DManagerBuilder]. - const DManagerBuilder() : super(DManager.new); + const DManagerBuilder() : super(); + + /// {@macro bro_abstract_manager.AbsManagerBuilder.create} + @override + DManager create() => DManager(); /// {@macro bro_abstract_manager.AbsManagerBuilder.getDependencies} @override diff --git a/bro_global_manager/test/mock/services/e_manager.dart b/bro_global_manager/test/mock/services/e_manager.dart index 04d536b..6cffe97 100644 --- a/bro_global_manager/test/mock/services/e_manager.dart +++ b/bro_global_manager/test/mock/services/e_manager.dart @@ -10,7 +10,11 @@ import 'd_manager.dart'; /// Build a new [EManager]. class EManagerBuilder extends AbsManagerBuilder { /// Create a new [EManagerBuilder]. - const EManagerBuilder() : super(EManager.new); + const EManagerBuilder() : super(); + + /// {@macro bro_abstract_manager.AbsManagerBuilder.create} + @override + EManager create() => EManager(); /// {@macro bro_abstract_manager.AbsManagerBuilder.getDependencies} @override diff --git a/bro_global_manager/test/mock/services/f_logger_manager.dart b/bro_global_manager/test/mock/services/f_logger_manager.dart index 2d28cb1..88aa722 100644 --- a/bro_global_manager/test/mock/services/f_logger_manager.dart +++ b/bro_global_manager/test/mock/services/f_logger_manager.dart @@ -11,7 +11,11 @@ import 'g_manager.dart'; /// This is the builder to create the [FLoggerManager]. class FLoggerBuilder extends AbsLoggerBuilder { /// Create the [FLoggerBuilder]. - const FLoggerBuilder() : super(FLoggerManager.new); + const FLoggerBuilder() : super(); + + /// {@macro bro_abstract_manager.AbsManagerBuilder.create} + @override + FLoggerManager create() => FLoggerManager(); /// {@macro bro_abstract_manager.AbsManagerBuilder.getDependencies} @override diff --git a/bro_global_manager/test/mock/services/g_manager.dart b/bro_global_manager/test/mock/services/g_manager.dart index a68875a..61eba64 100644 --- a/bro_global_manager/test/mock/services/g_manager.dart +++ b/bro_global_manager/test/mock/services/g_manager.dart @@ -9,7 +9,11 @@ import '../mixins/mixin_life_cycle_status.dart'; /// Build a new [GManager]. class GManagerBuilder extends AbsManagerBuilder { /// Create a new [GManagerBuilder]. - const GManagerBuilder() : super(GManager.new); + const GManagerBuilder() : super(); + + /// {@macro bro_abstract_manager.AbsManagerBuilder.create} + @override + GManager create() => GManager(); /// {@macro bro_abstract_manager.AbsManagerBuilder.getDependencies} @override diff --git a/bro_logger_manager/README.md b/bro_logger_manager/README.md index 7de1ba9..91468b9 100644 --- a/bro_logger_manager/README.md +++ b/bro_logger_manager/README.md @@ -10,6 +10,7 @@ SPDX-License-Identifier: MIT - [Table of contents](#table-of-contents) - [Introduction](#introduction) +- [Configuration](#configuration) ## Introduction @@ -19,3 +20,11 @@ easily. This package is based on the [bro_abstract_manager](https://pub.dev/packages/bro_abstract_manager) and [bro_abstract_logger](https://pub.dev/packages/bro_abstract_logger) packages. + +## Configuration + +If you use the [LoggerManager](lib/src/services/logger_manager.dart) class, +you have to add the [MixinLoggerConfigs](lib/src/mixins/mixin_logger_configs.dart) to your +app config manager. + +Read the mixin, to see what you have to set in your config files. diff --git a/bro_logger_manager/lib/src/helpers/my_log_printer.dart b/bro_logger_manager/lib/src/helpers/my_log_printer.dart index 5f2ef62..8b5f685 100644 --- a/bro_logger_manager/lib/src/helpers/my_log_printer.dart +++ b/bro_logger_manager/lib/src/helpers/my_log_printer.dart @@ -2,15 +2,13 @@ // // SPDX-License-Identifier: MIT +import 'package:bro_abstract_logger/bro_abstract_logger.dart'; import 'package:bro_logger_manager/src/models/log_message.dart'; import 'package:bro_logger_manager/src/types/log_level_extension.dart'; import 'package:logger/logger.dart'; /// This class is a custom log printer that allows to print logs with a custom format. class MyLogPrinter extends LogPrinter { - /// The default separator used to separate the categories. - static const _defaultCategorySeparator = "."; - /// Transform the [event] into a list of strings. @override List log(LogEvent event) { @@ -21,19 +19,13 @@ class MyLogPrinter extends LogPrinter { messageContent = messageContent.message; } - var message = "${DateTime.now().toUtc().toIso8601String()} - " - "[${event.level.logsLevel.name.toLowerCase()}]"; - - if (categories.isNotEmpty) { - message += " [${categories.join(_defaultCategorySeparator)}]"; - } - - message += ": $messageContent"; - - return [ - message, - if (event.error != null) "Error: ${event.error}", - if (event.stackTrace != null) "Stack trace: ${event.stackTrace}", - ]; + return LogFormatUtility.formatLogMessages( + message: messageContent, + exception: event.error, + stackTrace: event.stackTrace, + categories: categories, + level: event.level.logsLevel, + time: event.time, + ); } } diff --git a/bro_logger_manager/lib/src/services/logger_manager.dart b/bro_logger_manager/lib/src/services/logger_manager.dart index 3812219..6016bda 100644 --- a/bro_logger_manager/lib/src/services/logger_manager.dart +++ b/bro_logger_manager/lib/src/services/logger_manager.dart @@ -3,29 +3,40 @@ // SPDX-License-Identifier: MIT import 'package:bro_abstract_logger/bro_abstract_logger.dart'; +import 'package:bro_config_manager/bro_config_manager.dart'; import 'package:bro_global_manager/bro_global_manager.dart'; import 'package:bro_logger_manager/src/helpers/external_logger.dart'; import 'package:bro_logger_manager/src/mixins/mixin_logger_configs.dart'; /// This class is the builder to create the [LoggerManager]. class LoggerManagerBuilder extends AbsLoggerBuilder { - /// Create the [LoggerManagerBuilder]. + /// This is the flag to indicate if the [LoggerManager] should register the Flutter non managed + /// errors. /// /// {@macro bro_abstract_logger.AbstractLoggerManager.registerFlutterNonManagedErrorsAttention} - LoggerManagerBuilder({ - bool registerFlutterNonManagedErrors = true, - }) : super( - () => LoggerManager( - configGetter: globalGetManager, - registerFlutterNonManagedErrors: registerFlutterNonManagedErrors, - ), - ); + final bool registerFlutterNonManagedErrors; + + /// Create the [LoggerManagerBuilder]. + const LoggerManagerBuilder({ + this.registerFlutterNonManagedErrors = true, + }) : super(); + + /// {@macro bro_abstract_manager.AbsManagerBuilder.create} + @override + LoggerManager create() => LoggerManager( + configGetter: globalGetManager, + registerFlutterNonManagedErrors: registerFlutterNonManagedErrors, + ); + /// {@macro bro_abstract_manager.AbsManagerBuilder.getDependencies} @override Iterable getDependencies() => [C]; } /// This is an implementation of the [AbstractLoggerManager] with the Logger library. +/// +/// To use this manager, you have to add the [MixinLoggerConfigs] to your implementation of +/// the [AbstractConfigManager]. class LoggerManager extends AbstractLoggerManager { /// Get the config manager linked to this logger manager. final MixinLoggerConfigs Function() _configGetter; diff --git a/bro_logger_manager/pubspec.yaml b/bro_logger_manager/pubspec.yaml index 0b04cf7..18d1d7b 100644 --- a/bro_logger_manager/pubspec.yaml +++ b/bro_logger_manager/pubspec.yaml @@ -5,7 +5,7 @@ name: bro_logger_manager version: 1.0.0 -description: This package contains implemntation of the `logger` with the manager. +description: This package contains implementation of the `logger` with the manager. repository: https://github.com/borlnov/flutterbrolibs topics: - logger @@ -31,7 +31,7 @@ dependencies: bro_global_manager: ^1.1.0 - bro_config_manager: ^1.0.1 + bro_config_manager: ^1.1.0 dev_dependencies: flutter_test: diff --git a/bro_logger_manager/test/mock/services/config_manager.dart b/bro_logger_manager/test/mock/services/config_manager.dart index a891006..b406dbe 100644 --- a/bro_logger_manager/test/mock/services/config_manager.dart +++ b/bro_logger_manager/test/mock/services/config_manager.dart @@ -9,7 +9,11 @@ import 'package:bro_logger_manager/bro_logger_manager.dart'; /// Create a builder for the [ConfigManager] class ConfigManagerBuilder extends AbsManagerBuilder { /// Create a builder for the [ConfigManager] - ConfigManagerBuilder() : super(ConfigManager.new); + ConfigManagerBuilder() : super(); + + /// {@macro bro_abstract_manager.AbsManagerBuilder.create} + @override + ConfigManager create() => ConfigManager(); /// {@macro bro_abstract_manager.AbsManagerBuilder.getDependencies} @override diff --git a/bro_logger_manager/test/mock/services/global_manager.dart b/bro_logger_manager/test/mock/services/global_manager.dart index 180f523..02ddf1a 100644 --- a/bro_logger_manager/test/mock/services/global_manager.dart +++ b/bro_logger_manager/test/mock/services/global_manager.dart @@ -21,6 +21,6 @@ class GlobalManager extends AbsGlobalManager { registerManager) { registerManager(ConfigManagerBuilder()); registerManager>( - LoggerManagerBuilder()); + const LoggerManagerBuilder()); } } diff --git a/bro_platform_utility/.gitignore b/bro_platform_utility/.gitignore new file mode 100644 index 0000000..77f9caa --- /dev/null +++ b/bro_platform_utility/.gitignore @@ -0,0 +1,35 @@ +# SPDX-FileCopyrightText: 2025 Benoit Rolandeau +# +# SPDX-License-Identifier: MIT + +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +build/ diff --git a/bro_platform_utility/.metadata b/bro_platform_utility/.metadata new file mode 100644 index 0000000..2a15a9b --- /dev/null +++ b/bro_platform_utility/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "17025dd88227cd9532c33fa78f5250d548d87e9a" + channel: "stable" + +project_type: package diff --git a/bro_platform_utility/.metadata.license b/bro_platform_utility/.metadata.license new file mode 100644 index 0000000..ff5c7fc --- /dev/null +++ b/bro_platform_utility/.metadata.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 Benoit Rolandeau + +SPDX-License-Identifier: MIT diff --git a/bro_platform_utility/CHANGELOG.md b/bro_platform_utility/CHANGELOG.md new file mode 100644 index 0000000..13cabb8 --- /dev/null +++ b/bro_platform_utility/CHANGELOG.md @@ -0,0 +1,12 @@ + + + + +## 1.0.0 + +- Initial release. diff --git a/bro_platform_utility/LICENSE b/bro_platform_utility/LICENSE new file mode 100644 index 0000000..d817195 --- /dev/null +++ b/bro_platform_utility/LICENSE @@ -0,0 +1,18 @@ +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/bro_platform_utility/README.md b/bro_platform_utility/README.md new file mode 100644 index 0000000..f9db570 --- /dev/null +++ b/bro_platform_utility/README.md @@ -0,0 +1,16 @@ + + +# Bro platform utility + +## Table of contents + +- [Table of contents](#table-of-contents) +- [Introduction](#introduction) + +## Introduction + +This package contains utility classes and method to help you with platform specific code. diff --git a/bro_platform_utility/analysis_options.yaml b/bro_platform_utility/analysis_options.yaml new file mode 100644 index 0000000..062cde8 --- /dev/null +++ b/bro_platform_utility/analysis_options.yaml @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: 2025 Benoit Rolandeau +# +# SPDX-License-Identifier: MIT + +include: ../analysis_options.yaml diff --git a/bro_platform_utility/lib/bro_platform_utility.dart b/bro_platform_utility/lib/bro_platform_utility.dart new file mode 100644 index 0000000..5bc430d --- /dev/null +++ b/bro_platform_utility/lib/bro_platform_utility.dart @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: 2025 Benoit Rolandeau +// +// SPDX-License-Identifier: MIT + +library; + +export 'src/errors/cant_guess_platform_error.dart'; +export 'src/types/platform_type.dart'; diff --git a/bro_platform_utility/lib/src/errors/cant_guess_platform_error.dart b/bro_platform_utility/lib/src/errors/cant_guess_platform_error.dart new file mode 100644 index 0000000..fcde187 --- /dev/null +++ b/bro_platform_utility/lib/src/errors/cant_guess_platform_error.dart @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: 2025 Benoit Rolandeau +// +// SPDX-License-Identifier: MIT + +/// Thrown when the platform can't be guessed. +class CantGuessPlatformError extends Error { + /// Get the string representation of the error. + @override + String toString() => "Can't guess the current platform."; +} diff --git a/bro_platform_utility/lib/src/types/platform_type.dart b/bro_platform_utility/lib/src/types/platform_type.dart new file mode 100644 index 0000000..fd00951 --- /dev/null +++ b/bro_platform_utility/lib/src/types/platform_type.dart @@ -0,0 +1,74 @@ +// SPDX-FileCopyrightText: 2025 Benoit Rolandeau +// +// SPDX-License-Identifier: MIT + +import 'dart:io'; + +import 'package:bro_platform_utility/src/errors/cant_guess_platform_error.dart'; +import 'package:flutter/foundation.dart'; + +/// The type of platform the app is running on. +enum PlatformType { + /// This means that the platform type is not relevant for the current context. + irrelevant, + + /// The app is running on an Android device. + android, + + /// The app is running on an iOS device. + iOS, + + /// The app is running on a web browser. + web, + + /// The app is running on a macOS device. + macOS, + + /// The app is running on a Windows device. + windows, + + /// The app is running on a Linux device. + linux, + + /// The app is running on a Fuchsia device. + fuchsia; + + /// Check if the current platform is supported by the manager. + /// + /// If [currentPlatform] is null, the current platform will be guessed using + /// [guessCurrentPlatform]. + static bool isCurrentSupported({ + PlatformType? currentPlatform, + required List supportedPlatforms, + }) { + final tmpCurrentPlatform = currentPlatform ?? guessCurrentPlatform(); + if (supportedPlatforms.contains(irrelevant)) { + return true; + } + return supportedPlatforms.contains(tmpCurrentPlatform); + } + + /// Guess the current platform of the app device. + static PlatformType guessCurrentPlatform() { + PlatformType? platformType; + if (Platform.isAndroid) { + platformType = android; + } else if (Platform.isIOS) { + platformType = iOS; + } else if (Platform.isMacOS) { + platformType = macOS; + } else if (Platform.isWindows) { + platformType = windows; + } else if (Platform.isLinux) { + platformType = linux; + } else if (Platform.isFuchsia) { + platformType = fuchsia; + } else if (kIsWeb) { + platformType = web; + } else { + throw CantGuessPlatformError(); + } + + return platformType; + } +} diff --git a/bro_platform_utility/mono_pkg.yaml b/bro_platform_utility/mono_pkg.yaml new file mode 100644 index 0000000..6aedc90 --- /dev/null +++ b/bro_platform_utility/mono_pkg.yaml @@ -0,0 +1,18 @@ +# SPDX-FileCopyrightText: 2025 Benoit Rolandeau +# +# SPDX-License-Identifier: MIT + +sdk: + - stable + +stages: + - analyze: + - group: + - analyze: --fatal-infos . + - command: + - dart pub global activate pana + # TODO(borlnov): Add `--exit-code-threshold 20` when the issue: + # https://github.com/dart-lang/pana/issues/1020, is fixed. + - pana --line-length 100 . + - unit_test: + - test: --flavor test . diff --git a/bro_platform_utility/pubspec.yaml b/bro_platform_utility/pubspec.yaml new file mode 100644 index 0000000..f2bc1e2 --- /dev/null +++ b/bro_platform_utility/pubspec.yaml @@ -0,0 +1,30 @@ +# SPDX-FileCopyrightText: 2025 Benoit Rolandeau +# +# SPDX-License-Identifier: MIT + +name: bro_platform_utility +version: "1.0.0" + +description: Contains specific platform utility functions and classes to use in your app. +repository: https://github.com/borlnov/flutterbrolibs +topics: + - platform + - brolibs + - shared + +environment: + sdk: ^3.6.0 + flutter: ">=1.17.0" + +resolution: workspace + +dependencies: + flutter: + sdk: flutter + + platform: ^3.1.6 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^5.0.0 diff --git a/bro_platform_utility/test/platform_type_test.dart b/bro_platform_utility/test/platform_type_test.dart new file mode 100644 index 0000000..237f2a4 --- /dev/null +++ b/bro_platform_utility/test/platform_type_test.dart @@ -0,0 +1,71 @@ +// SPDX-FileCopyrightText: 2025 Benoit Rolandeau +// +// SPDX-License-Identifier: MIT + +import 'package:bro_platform_utility/bro_platform_utility.dart'; +import 'package:flutter_test/flutter_test.dart'; + +/// This is the test for the [PlatformType] class. +void main() { + group("Test the `isCurrentSupported` method", () { + test("Test the results of the method", () { + expect( + PlatformType.isCurrentSupported( + currentPlatform: PlatformType.android, + supportedPlatforms: [PlatformType.android], + ), + true, + reason: "test if the current platform is supported, with one element in the list", + ); + expect( + PlatformType.isCurrentSupported( + currentPlatform: PlatformType.iOS, + supportedPlatforms: [PlatformType.android, PlatformType.iOS], + ), + true, + reason: "the current platform is supported, with multiple elements in the list", + ); + expect( + PlatformType.isCurrentSupported( + currentPlatform: PlatformType.web, + supportedPlatforms: [], + ), + false, + reason: "the current platform isn't supported, with an empty list", + ); + expect( + PlatformType.isCurrentSupported( + currentPlatform: PlatformType.web, + supportedPlatforms: [PlatformType.windows, PlatformType.linux], + ), + false, + reason: "the current platform isn't supported", + ); + expect( + PlatformType.isCurrentSupported( + currentPlatform: PlatformType.windows, + supportedPlatforms: [PlatformType.irrelevant], + ), + true, + reason: "the current platform is supported, with irrelevant", + ); + expect( + PlatformType.isCurrentSupported( + currentPlatform: PlatformType.irrelevant, + supportedPlatforms: [PlatformType.windows, PlatformType.irrelevant, PlatformType.web], + ), + true, + reason: "the current platform is supported, with irrelevant surrounded by other platforms", + ); + }); + test("Test the platform guessing", () { + expect( + PlatformType.isCurrentSupported( + supportedPlatforms: [PlatformType.guessCurrentPlatform()], + ), + true, + reason: "the current platform is supported, with the guessed platform", + ); + }); + }); +} diff --git a/bro_yaml_utility/CHANGELOG.md b/bro_yaml_utility/CHANGELOG.md index 4d45b5e..481eeb9 100644 --- a/bro_yaml_utility/CHANGELOG.md +++ b/bro_yaml_utility/CHANGELOG.md @@ -1,5 +1,5 @@ @@ -10,6 +10,7 @@ SPDX-License-Identifier: MIT ## 1.1.0 - Update logger dependencies +- Fix problem with merging method and abusive warning message display ## 1.0.2 diff --git a/bro_yaml_utility/lib/src/json_utility.dart b/bro_yaml_utility/lib/src/json_utility.dart index 7fb099f..a578fed 100644 --- a/bro_yaml_utility/lib/src/json_utility.dart +++ b/bro_yaml_utility/lib/src/json_utility.dart @@ -49,9 +49,11 @@ abstract final class JsonUtility { continue; } - logger?.warn("Json merging: the key $key is already present in the base map, but not with " - "the same type. The base type: ${baseValue.runtimeType}, the override " - "type: ${overValue.runtimeType}"); + if (baseValue.runtimeType != overValue.runtimeType) { + logger?.warn("Json merging: the key $key is already present in the base map, but not with " + "the same type. The base type: ${baseValue.runtimeType}, the override " + "type: ${overValue.runtimeType}"); + } mergedJson[key] = overValue; } diff --git a/pubspec.yaml b/pubspec.yaml index 61ea5a4..27ce561 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -14,9 +14,12 @@ workspace: - bro_abstract_manager - bro_config_manager - bro_file_utility + - bro_firebase_core + - bro_firebase_crashlytics - bro_global_manager - bro_list_utility - bro_logger_manager + - bro_platform_utility - bro_types_utility - bro_yaml_utility