tremp
tremp

Reputation: 371

PlatformDispatcher.instance.onError vs runZonedGuarded

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

Answers (2)

Umut
Umut

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:

Option #1 PlatformDispatcher.instance.onError

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.

Option #2 runZonedGuarded

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

Conclusion

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

Johannes Fahrenkrug
Johannes Fahrenkrug

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

Related Questions