Fix crashlytics in game_template (#1998)

pull/2003/head
Filip Hracek 10 months ago committed by GitHub
parent b0f2a6a86d
commit 612cf191d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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.

@ -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<void> 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,

@ -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<void> 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<Future<void>>(() 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<dynamic>;
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;
}

@ -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

@ -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.<fn>.<fn>(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'));
});
});
}
Loading…
Cancel
Save