Reputation: 371
Can anyone explain(or provide a link to explaining documentation) when i must use PlatformDispatcher.instance.onError and when runZonedGuarded? As I understand it, they are both about handling async exceptions....
Upvotes: 10
Views: 2274
Reputation: 94
Update (2023-07-04):
Current version of very good cli(0.15.0 as of writing) is not using runZonedGuarded. I think both parties moved away from the mentioned function because of it's complicated usage or not being friendly with WidgetsFlutterBinding.ensureInitialized(). But both options are still working. So once again as stated in the original answer:
As always do your own research/testing and choose the option according to your needs.
Original answer:
I was searching an answer for the same question, and I think I've found a somewhat satisfying answer:
https://api.flutter.dev/flutter/dart-ui/PlatformDispatcher/onError.html
Example from https://firebase.google.com/docs/crashlytics/get-started?platform=flutter
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
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;
};
runApp(MyApp());
}
But an important quote from https://api.flutter.dev/flutter/dart-ui/PlatformDispatcher/instance.html
Consider avoiding static references to this singleton though PlatformDispatcher.instance and instead prefer using a binding for dependency resolution such as WidgetsBinding.instance.platformDispatcher.
Static access of this object means that Flutter has few, if any options to fake or mock the given object in tests. Even in cases where Dart offers special language constructs to forcefully shadow such properties, those mechanisms would only be reasonable for tests and they would not be reasonable for a future of Flutter where we legitimately want to select an appropriate implementation at runtime.
The only place that WidgetsBinding.instance.platformDispatcher is inappropriate is if access to these APIs is required before the binding is initialized by invoking runApp() or WidgetsFlutterBinding.instance.ensureInitialized(). In that case, it is necessary (though unfortunate) to use the PlatformDispatcher.instance object statically.
https://api.dart.dev/stable/2.18.7/dart-async/runZonedGuarded.html
We also have another option as in your question which is used in very good cli.
Future<void> bootstrap(FutureOr<Widget> Function() builder) async {
FlutterError.onError = (details) {
log(details.exceptionAsString(), stackTrace: details.stack);
};
Bloc.observer = AppBlocObserver();
await runZonedGuarded(
() async => runApp(await builder()),
(error, stackTrace) => log(error.toString(), stackTrace: stackTrace),
);
}
Unfortunately this doesn't work if you use WidgetsFlutterBinding.ensureInitialized();
outside of the runZonedGuarded
method. So keep that in mind. Some issue links:
https://github.com/firebase/flutterfire/issues/6964#issuecomment-915935180
https://github.com/flutter/flutter/issues/48972
Since the docs state that PlatformDispatcher.instance
usage as unfortunate, I guess we can conclude that using runZonedGuarded
is the better option.
As always do your own research/testing and choose the option according to your needs.
Upvotes: 3
Reputation: 44818
I got a warning after upgrading to Flutter 3.10.5:
════════ Exception caught by Flutter framework ═════════════════════════════════
The following assertion was thrown during runApp:
Zone mismatch.
The Flutter bindings were initialized in a different zone than is now being used. This will likely cause confusion and bugs as any zone-specific configuration will inconsistently use the configuration of the original binding initialization zone or this zone based on hard-to-predict factors such as which zone was active when a particular callback was set.
It is important to use the same zone when calling `ensureInitialized` on the binding as when calling `runApp` later.
To make this warning fatal, set BindingBase.debugZoneErrorsAreFatal to true before the bindings are initialized (i.e. as the first statement in `void main() { }`).
When the exception was thrown, this was the stack
#0 BindingBase.debugCheckZone.<anonymous closure>
binding.dart:497
#1 BindingBase.debugCheckZone
binding.dart:502
#2 runApp
binding.dart:1080
#3 main.<anonymous closure>
main.dart:52
#4 FirebaseDiagnosticsService.initCrashlytics.<anonymous closure>
firebase_diagnostics_service.dart:53
<asynchronous suspension>
════════════════════════════════════════════════════════════════════════════════
I was using runZonedGuarded
. So I did some research. It turns out that using runZonedGuarded
for Crashlytics is deprecated. You can see that in this commit where all references to runZonedGuarded
were replaced:
https://github.com/firebase/flutterfire/commit/8a0caa05d5abf6fef5bf0e654654dcd0b6ec874a
Also note that the current official documentation doesn't mention runZonedGuarded
anymore: https://firebase.google.com/docs/crashlytics/customize-crash-reports?platform=flutter
The recommended way is this:
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
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;
};
runApp(MyApp());
}
Enjoy!
Upvotes: 1