diff --git a/game_template/README.md b/game_template/README.md index 9504a188d..d29a17f25 100644 --- a/game_template/README.md +++ b/game_template/README.md @@ -49,7 +49,6 @@ lib │   ├── ads │   ├── app_lifecycle │   ├── audio -│   ├── crashlytics │   ├── game_internals │   ├── games_services │   ├── in_app_purchase @@ -240,12 +239,9 @@ with their mouth. ## Crashlytics -Crashlytics integration is disabled by default. But even if you don't -enable it, you might find code in `lib/src/crashlytics` helpful. -It gathers all log messages and errors, so that you can, at the very least, -print them to the console. +Crashlytics integration is disabled by default. -When enabled, this integration is a lot more powerful: +When enabled, this integration is quite powerful: - Any crashes of your app are sent to the Firebase Crashlytics console. - Any uncaught exception thrown anywhere in your code is captured @@ -256,14 +252,6 @@ When enabled, this integration is a lot more powerful: - Device model, orientation, RAM free, disk free - Operating system version - App version -- In addition, log messages generated anywhere in your app - (and from packages you use) are recorded in memory, - and are sent alongside the reports. This means that you can - learn what happened before the crash or exception - occurred. -- Also, any generated log message with `Level.severe` or above - is also sent to Crashlytics. -- You can customize these behaviors in `lib/src/crashlytics`. To enable Firebase Crashlytics, do the following: @@ -273,7 +261,7 @@ To enable Firebase Crashlytics, do the following: You don't need to enable Analytics in the project if you don't want to. 2. [Install `firebase-tools`](https://firebase.google.com/docs/cli?authuser=0#setup_update_cli) on your machine. -3. [Install `flutterfire` CLI](https://firebase.flutter.dev/docs/cli#installation) +3. [Install `flutterfire` CLI](https://firebase.google.com/docs/flutter/setup) on your machine. 4. In the root of this project (the directory containing `pubspec.yaml`), run the following: @@ -286,8 +274,7 @@ To enable Firebase Crashlytics, do the following: the correct code. 5. Go to `lib/main.dart` and uncomment the lines that relate to Crashlytics. -You should now be able to see crashes, errors, and -severe log messages in +You should now be able to see crashes and errors in [console.firebase.google.com](https://console.firebase.google.com/). To test, add a button to your project, and throw whatever exception you like when the player presses it. diff --git a/game_template/lib/main.dart b/game_template/lib/main.dart index bb548fbaf..8b6518dbd 100644 --- a/game_template/lib/main.dart +++ b/game_template/lib/main.dart @@ -5,9 +5,12 @@ // Uncomment the following lines when enabling Firebase Crashlytics // import 'dart:io'; // import 'package:firebase_core/firebase_core.dart'; +// import 'package:firebase_crashlytics/firebase_crashlytics.dart'; +// import 'package:flutter/foundation.dart'; // import 'firebase_options.dart'; -import 'package:firebase_crashlytics/firebase_crashlytics.dart'; +import 'dart:developer' as dev; + import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:go_router/go_router.dart'; @@ -17,7 +20,6 @@ import 'package:provider/provider.dart'; import 'src/ads/ads_controller.dart'; import 'src/app_lifecycle/app_lifecycle.dart'; import 'src/audio/audio_controller.dart'; -import 'src/crashlytics/crashlytics.dart'; import 'src/games_services/games_services.dart'; import 'src/games_services/score.dart'; import 'src/in_app_purchase/in_app_purchase.dart'; @@ -38,33 +40,45 @@ import 'src/style/snack_bar.dart'; import 'src/win_game/win_game_screen.dart'; Future main() async { - // To enable Firebase Crashlytics, uncomment the following lines and - // the import statements at the top of this file. + // Subscribe to log messages. + Logger.root.onRecord.listen((record) { + dev.log( + record.message, + time: record.time, + level: record.level.value, + name: record.loggerName, + zone: record.zone, + error: record.error, + stackTrace: record.stackTrace, + ); + }); + + WidgetsFlutterBinding.ensureInitialized(); + + // TODO: To enable Firebase Crashlytics, uncomment the following line. // See the 'Crashlytics' section of the main README.md file for details. - FirebaseCrashlytics? crashlytics; // if (!kIsWeb && (Platform.isIOS || Platform.isAndroid)) { // try { - // WidgetsFlutterBinding.ensureInitialized(); // await Firebase.initializeApp( // options: DefaultFirebaseOptions.currentPlatform, // ); - // crashlytics = FirebaseCrashlytics.instance; + // + // FlutterError.onError = (errorDetails) { + // FirebaseCrashlytics.instance.recordFlutterFatalError(errorDetails); + // }; + // + // // Pass all uncaught asynchronous errors + // // that aren't handled by the Flutter framework to Crashlytics. + // PlatformDispatcher.instance.onError = (error, stack) { + // FirebaseCrashlytics.instance.recordError(error, stack, fatal: true); + // return true; + // }; // } catch (e) { // debugPrint("Firebase couldn't be initialized: $e"); // } // } - await guardWithCrashlytics( - guardedMain, - crashlytics: crashlytics, - ); -} - -/// Without logging and crash reporting, this would be `void main()`. -void guardedMain() { - WidgetsFlutterBinding.ensureInitialized(); - _log.info('Going full screen'); SystemChrome.setEnabledSystemUIMode( SystemUiMode.edgeToEdge, diff --git a/game_template/lib/src/crashlytics/crashlytics.dart b/game_template/lib/src/crashlytics/crashlytics.dart deleted file mode 100644 index fd2d5d7d5..000000000 --- a/game_template/lib/src/crashlytics/crashlytics.dart +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright 2022, the Flutter project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -import 'dart:async'; -import 'dart:isolate'; - -import 'package:firebase_crashlytics/firebase_crashlytics.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/widgets.dart'; -import 'package:logging/logging.dart'; - -/// Runs [mainFunction] in a guarded [Zone]. -/// -/// If a non-null [FirebaseCrashlytics] instance is provided through -/// [crashlytics], then all errors will be reported through it. -/// -/// These errors will also include latest logs from anywhere in the app -/// that use `package:logging`. -Future guardWithCrashlytics( - void Function() mainFunction, { - required FirebaseCrashlytics? crashlytics, -}) async { - // Running the initialization code and [mainFunction] inside a guarded - // zone, so that all errors (even those occurring in callbacks) are - // caught and can be sent to Crashlytics. - await runZonedGuarded>(() async { - if (kDebugMode) { - // Log more when in debug mode. - Logger.root.level = Level.FINE; - } - // Subscribe to log messages. - Logger.root.onRecord.listen((record) { - final message = '${record.level.name}: ${record.time}: ' - '${record.loggerName}: ' - '${record.message}'; - - debugPrint(message); - // Add the message to the rotating Crashlytics log. - crashlytics?.log(message); - - if (record.level >= Level.SEVERE) { - crashlytics?.recordError(message, filterStackTrace(StackTrace.current), - fatal: true); - } - }); - - // Pass all uncaught errors from the framework to Crashlytics. - if (crashlytics != null) { - WidgetsFlutterBinding.ensureInitialized(); - FlutterError.onError = crashlytics.recordFlutterFatalError; - } - - if (!kIsWeb) { - // To catch errors outside of the Flutter context, we attach an error - // listener to the current isolate. - Isolate.current.addErrorListener(RawReceivePort((dynamic pair) async { - final errorAndStacktrace = pair as List; - await crashlytics?.recordError( - errorAndStacktrace.first, errorAndStacktrace.last as StackTrace?, - fatal: true); - }).sendPort); - } - - // Run the actual code. - mainFunction(); - }, (error, stack) { - // This sees all errors that occur in the runZonedGuarded zone. - debugPrint('ERROR: $error\n\n' - 'STACK:$stack'); - crashlytics?.recordError(error, stack, fatal: true); - }); -} - -/// Takes a [stackTrace] and creates a new one, but without the lines that -/// have to do with this file and logging. This way, Crashlytics won't group -/// all messages that come from this file into one big heap just because -/// the head of the StackTrace is identical. -/// -/// See this: -/// https://stackoverflow.com/questions/47654410/how-to-effectively-group-non-fatal-exceptions-in-crashlytics-fabrics. -@visibleForTesting -StackTrace filterStackTrace(StackTrace stackTrace) { - try { - final lines = stackTrace.toString().split('\n'); - final buf = StringBuffer(); - for (final line in lines) { - if (line.contains('crashlytics.dart') || - line.contains('_BroadcastStreamController.java') || - line.contains('logger.dart')) { - continue; - } - buf.writeln(line); - } - return StackTrace.fromString(buf.toString()); - } catch (e) { - debugPrint('Problem while filtering stack trace: $e'); - } - - // If there was an error while filtering, - // return the original, unfiltered stack track. - return stackTrace; -} diff --git a/game_template/pubspec.yaml b/game_template/pubspec.yaml index 0172c8ef4..c42f166eb 100644 --- a/game_template/pubspec.yaml +++ b/game_template/pubspec.yaml @@ -13,9 +13,9 @@ dependencies: flutter: sdk: flutter - audioplayers: ^5.0.0 + audioplayers: ^5.1.0 cupertino_icons: ^1.0.2 - go_router: ^10.0.0 + go_router: ^10.1.2 logging: ^1.1.0 provider: ^6.0.2 shared_preferences: ^2.0.13 @@ -24,7 +24,7 @@ dependencies: # delete the relevant line below, and get rid of any Dart code # that references the dependency. firebase_core: ^2.1.1 # Needed for Crashlytics below - firebase_crashlytics: ^3.0.3 # Error reporting + firebase_crashlytics: ^3.3.5 # Error reporting games_services: ^3.0.0 # Achievements and leaderboards google_mobile_ads: ^3.0.0 # Ads in_app_purchase: ^3.0.1 # In-app purchases diff --git a/game_template/test/crashlytics_test.dart b/game_template/test/crashlytics_test.dart deleted file mode 100644 index 9a08dcd44..000000000 --- a/game_template/test/crashlytics_test.dart +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2022, the Flutter project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -import 'package:game_template/src/crashlytics/crashlytics.dart'; -import 'package:test/test.dart'; - -void main() { - group('filterStackTrace', () { - test('keeps current stacktrace intact', () { - final original = StackTrace.current; - final filtered = filterStackTrace(original).toString().trim(); - - expect(filtered, equals(original.toString().trim())); - }); - - test('parses an empty stacktrace', () { - const original = StackTrace.empty; - final filtered = filterStackTrace(original).toString().trim(); - - expect(filtered, equals(original.toString().trim())); - }); - - test('removes the head of an example stacktrace', () { - final original = StackTrace.fromString( - ''' at guardWithCrashlytics..(crashlytics.dart:32) - at _BroadcastStreamController.add(_BroadcastStreamController.java) - at Logger._publish(logger.dart:276) - at Logger.log(logger.dart:200) - at Logger.severe(logger.dart:258) - at GamesServicesController.initialize(games_services.dart:23)'''); - final filtered = filterStackTrace(original).toString().trim(); - - expect(filtered, isNot(original.toString().trim())); - expect(filtered, isNot(contains('at guardWithCrashlytics'))); - expect(filtered, contains('at GamesServicesController')); - }); - }); -}