Error handling in Flutter plugins

I’ve just figured out Flutter error handling for method channels communicating with native code and wanted to capture the technique. This is something that I missed despite a fair amount of reading on plugin integration, so others might have missed it as well. There’s a very good post, Flutter Platform Channels, where I got much of this information, definitely worth a read. There are some details that it doesn’t cover, particularly about testing, and it’s not just focused on error handling, so I thought I’d boost the signal on that.

Error structure

An error communicated across a method channel is defined as three properties:

  • Code: A string error code used in error handling code to differentiate the behaviour.
  • Message: A string intended for human consumption, gives information about the error.
  • Details: A string, for slightly more technical human consumption, that provides more detailed information about the exception.

Dart

In Dart an error is represented as a PlatformException. When invoking a method on a MethodChannel a PlatformException is thrown if the error encoding is detected in a result. The Swift and Kotlin sections that follow show how to send an error encoding as the result of a method invocation. Here’s an example of handling an exception.

MethodChannel channel = MethodChannel( 'my_channel_name' );
try {
  await channel.invokeMethod( 'myMethod', 10 );
} on PlatformException catch(e) {
  if ( e.code == "someKnownError" ) {
    // handle it
  }
}

In unit tests it’s easy to test error handling conditions by throwing this exception from the channel method handler. Here’s an example:

setUp(() {
    channel.setMockMethodCallHandler((MethodCall methodCall) async {
      if (methodCall.method == "myMethod") {
        int max = methodCall.arguments
        if ( max <= 0 ) {
          throw PlatformException(
              code: "missingOrInvalidArg", 
              message: "max must be >= 0");
        }
        // otherwise return the expected result
        ... 
      }
    });
  });

Swift

In Swift an error is sent to the result function as a FlutterError object. The handle method of a Swift plugin is given the function result: FlutterResult as a parameter. If the method invocation is successful then that result method is invoked with whatever a successful return looks like, could just be a string like: result( “some useful data”). If there’s an error while handling a method the same result method is used, but with a Flutter error object as shown here:

FlutterError( code: String, message: String, details: String )
   switch call.method {
     case "myMethod": 
       guard let max = call.arguments as? Int else {
            result(
              FlutterError( code: "invalidArgs", 
                message: "Missing arg max",
                details: "Expected 1 Int arg." ))
            return
       }
       myMethod( max, result )
     default: 
       result( FlutterMethodNotImplemented)
  }

Note that there is a constant, FlutterMethodNotImplemented, that should be used for the special case of an unimplemented method. It is shown above in the default handler.

Kotlin

In Kotlin error handling is a bit cleaner because there is an explicit error method on the FlutterResult class.

/**
         * Handles an error result.
         *
         * @param errorCode An error code String.
         * @param errorMessage A human-readable error message String, possibly null.
         * @param errorDetails Error details, possibly null
         */
        @UiThreadvoid error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails);
if (call.method == "myMethod") {
  if ( null != call.arguments && call.arguments is Integer ) {
    max = call.arguments as Integer
    myMethod( max, result )
  }
  else {
    result.error( "invalidArgs", "Missing arg max", "Expected 1 Integer arg."
  }
}

Leave a Reply