This article describes a pit you can easily fall into when using Dart asynchronous functions and try-catch exception handling. And it might help to analyze and resolve some Dart crashes related to asynchronous calls.

A small test

What’s wrong with writing platform Channel calls and handling exceptions like this?

const MethodChannel channel = MethodChannel('my_platform_channel');
try {
  channel.invokeMethod('my_method');
} catch (e) {
  debugPrint('Caught an exception: $e'); 
}
Copy the code

The results scroll down:

The code that uses a try-catch is not going to work, it’s not going to go into a catch block! And the log says Unhandled Exception.

Because channel.invokeMethod is an asynchronous function, its return value is Future:

// omit the long official function documentFuture<T? > invokeMethod<T>(String method, [ dynamic arguments ]) {
  return _invokeMethod<T>(method, missingOk: false, arguments: arguments);

}
Copy the code

For more analysis and correct writing, see below.

Best practices

  1. If you call an asynchronous function (a function that returns a Future) with await and need to do exception handling, just try-catch outside of await
  2. If you are calling an asynchronous function (a function that returns a Future) without await and need to do exception handling, never try catch directly (otherwise it will not catch and the code will continue to mislead the next person), useonErrororcatchErrorDo the corresponding processing or re-throw.

background

A platform call exception to a webview_flutter and a Settings plugin is investigated. The two exceptions are very similar:

The following exceptions exist:

MissingPluginException(No implementation found for method evaluateJavascript on channel plugins.flutter.io/webview_0)
#0      MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:253)
<asynchronous suspension>
// There is no line number for the caller
Copy the code

This should be a call similar to channel.invokemethod (‘evaluateJavascript’) mentioned above. After looking at the Dart code for invokeMethod that appears in the WebView Plugin section, the code reads like this:

// js_bridge.dart
            """ // omit some js code} return result; }) ($s"" ";
  try {
    _webViewController.evaluateJavascript(js);
  } catch(e) {
  }
}
Copy the code

The implementation in evaluateJavascript looks like this

@override
Future<String> evaluateJavascript(String javascriptString) {
  return _channel.invokeMethod<String> ('evaluateJavascript', javascriptString);
}
Copy the code

The first response is 🤔 try-catch why didn’t you catch the exception?

Five tests were done

Look at the following test code for handling exceptions in five cases where getPlatformVersion is deliberately named for an unregistered channel method and must throw a MissingPluginException.

So guess what happens when you write each of these five ways?

class FlutterPluginDemo {
  static const MethodChannel _channel =
      const MethodChannel('flutter_plugin_demo');

  static Future<String> get platformVersion async {
    var future;
    // The first type of error handling: error
    try {
      future = _channel.invokeMethod('getPlatformVersion');
      print('1. future.type: ${future.runtimeType}'); // Invoke returns a Future
    } catch (e) {
      // So there is almost no way to get there
      print('1. caught a exception: $e');
    }

    // Error # 2: true
    try {
      var result = await future;
      print('2. result.type: ${result.runtimeType}');
    } catch (e) {
      print('2. caught a exception by try-catch: $e'); // This will catch the exception of the first future correctly
    }

    // Error handling number 3: true
    _channel.invokeMethod('getPlatformVersion').onError(
        (error, stackTrace) => print('3. caught a exception by onError: $error'));

    
      
        cannot find the line number of the source code to call
      
    _channel.invokeMethod('getPlatformVersion');

    Asynchro = asynchro = asynchro = asynchro = asynchro = asynchro = asynchro = asynchro = asynchro = asynchro = asynchro
    await _channel.invokeMethod('getPlatformVersion');
    return "test"; }}Copy the code

The results (see picture) :

That is:

  • The first is incorrect, the catch block is never called, and the type of the future variable isFuture<dynamic>
  • The second, which is correct, is to take the Future and await it, and the catch will catch normally
  • The third, correct, is that onError is a method of the Future that can be turned into a lambda function that handles the exception when the exception is thrown internally, and the exception transitions from unhandled to handled state
  • The fourth type, which is not recommended, raises the following exception in the log without await and exception handling:
com.eggfly.flutter_plugin_demo_example E/flutter: [ERROR:flutter/lib/ui/ui_dart_state.cc(186)] Unhandled Exception: MissingPluginException(No implementation found for method getPlatformVersion on channel flutter_plugin_demo)
    #0      MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:156:7)
    <asynchronous suspension>
Copy the code

Dart: platform_channel.dart: platform_channel.dart: platform_channel.dart: platform_channel.dart: platform_channel.dart: platform_channel.dart: platform_channel.dart: platform_channel.dart

  • The fifth type, which is not recommended, uses await but does not have onError or try-catch to handle the exception. The following exception information may appear in the log when an exception is encountered:
com.eggfly.flutter_plugin_demo_example E/flutter: [ERROR:flutter/lib/ui/ui_dart_state.cc(186)] Unhandled Exception: MissingPluginException(No implementation found for method getPlatformVersion on channel flutter_plugin_demo)

    #0      MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:156:7)

    <asynchronous suspension>

    #1      FlutterPluginDemo.platformVersion (package:flutter_plugin_demo/flutter_plugin_demo.dart:35:5)

    <asynchronous suspension>

    #2      _MyAppState.initPlatformState (package:flutter_plugin_demo_example/main.dart:30:25)

    <asynchronous suspension>
Copy the code

Notice that in this case with await, there is nested asynchronous call information inside. This can be very helpful in resolving Dart exceptions.

The solution

  • Dart exception for webview_flutter can be changed to:
// try catch cannot catch exceptions using Future
_webViewController.evaluateJavascript(js).catchError((e) {});
Copy the code

Note: In this evaluateJavascript scenario catchError is not being processed;

In other scenarios, you can distinguish between specific exceptions by type and handle or re-throw them accordingly.

Read more:

  • Official documentation: Future and error handling