Skip to content

[PlatformChannel] Support Dart-side handling of errors/exceptions thrown during Dart-side method call handling #144954

Open

Description

@ChristianEdwardPadilla

Internal: b/327780213

Use case

Consider an unhandled Exception being thrown in a Flutter app's main Dart isolate. Typically, this Exception is expected to be handled by one or more of these three mechanisms:

  1. binding.platformDispatcher.onError
  2. Isolate.current.addErrorListener
  3. runZoneGuarded's onError

The following example app shows an exception that surprisingly isn't handled by any of the above mechanisms:

import 'dart:async';
import 'dart:isolate';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() async {
  final binding = WidgetsFlutterBinding.ensureInitialized();
  binding.platformDispatcher.onError = (error, stackTrace) {
    // boom is not caught here.
    print('blah platformDispatcher got error $error');
    return true;
  };
  Isolate.current.addErrorListener(
    RawReceivePort((dynamic pair) {
      final errorAndStackTrace = (pair as List<Object>).cast<String>();
      // boom is not caught here.
      print('blah isolate $errorAndStackTrace');
    }).sendPort,
  );

  await runZonedGuarded(() async {
    runApp(Container(color: Colors.red));

    // Create the two method channels used in this demo.
    const ping = MethodChannel('ping');
    const pong = MethodChannel('pong');
    
    // Throw an exception when the platform calls the `pong` channel.
    pong.setMethodCallHandler((call) async {
      throw Exception('boom');
    });

    // Kick-off the `pong` call by calling the `ping` channel
    await ping.invokeMethod('');
  }, (error, st) {
    // boom is not caught here.
    print('blah runZoneGuarded $error, $st');
  });

Instead, the exception is forwarded to the platform for handling via the MethodChannel, example implementation for Android:

package com.google.android.flutter.samples.minimal;

import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.Result;

public class MainActivity extends FlutterActivity {
  @Override
  public void configureFlutterEngine(FlutterEngine flutterEngine) {
    PluginRegistrant.registerWith(flutterEngine);
    var ping = new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), "ping");
    var pong = new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), "pong");

    ping.setMethodCallHandler(
        (call, result) -> {
          result.success(null);
          pong.invokeMethod(
              /* method */ "boom",
              /* arguments */ null,
              // Without this, the "boom" error in Dart will be silently dropped.
              /* callback */ new Result() {
                @Override
                public void success(Object result) {
                  android.util.Log.w("MinimalActivity", "blah success");
                }

                @Override
                public void error(String errorCode, String errorMessage, Object errorDetails) {
                  android.util.Log.w(
                      "MinimalActivity",
                      "blah error errorCode="
                          + errorCode
                          + " errorMessage="
                          + errorMessage
                          + " errorDetails="
                          + errorDetails);
                }

                @Override
                public void notImplemented() {
                  android.util.Log.w("MinimalActivity", "blah notimplemented");
                }
              });
        });
  }
}

Proposal

Instead of always forwarding exceptions thrown in Dart method call handlers back to the platform, provide an otpional mechanism for those exceptions to escape and be handled in Dart.

Activity

  1. added
    engineflutter/engine related. See also e: labels.
    c: proposalA detailed proposal for a change to Flutter
    a: pluginsSupport for writing, building, and running plugin packages
    fyi-engineFor the attention of Engine team
    P2Important issues not at the top of the work list
    on Mar 11, 2024
  2. stuartmorgan-g commented on Mar 12, 2024

    @stuartmorgan-g
    Contributor

    Instead of always forwarding exceptions thrown in Dart method call handlers back to the platform, provide an otpional mechanism for those exceptions to escape and be handled in Dart.

    Based on our own experience in our plugins, having the exceptions stay on the Dart side is probably a better outcome for most of our use cases as well; this definitely seems like a reasonable proposal.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Important issues not at the top of the work lista: pluginsSupport for writing, building, and running plugin packagesc: proposalA detailed proposal for a change to Flutterengineflutter/engine related. See also e: labels.team-ecosystemOwned by Ecosystem teamtriaged-ecosystemTriaged by Ecosystem team

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions