mirror of
https://github.com/flutter/samples.git
synced 2025-11-08 13:58:47 +00:00
Fix crashlytics in game_template (#1998)
This commit is contained in:
@@ -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'));
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user