invokeMethod<T> method Null safety

  1. @optionalTypeArgs
Future<T?> invokeMethod<T>(
  1. String method,
  2. [dynamic arguments]
)
optionalTypeArgs">@optionalTypeArgs

Invokes a method on this channel with the specified arguments.

The static type of arguments is dynamic, but only values supported by the codec of this channel can be used. The same applies to the returned result. The values supported by the default codec and their platform-specific counterparts are documented with StandardMessageCodec.

The generic argument T of the method can be inferred by the surrounding context, or provided explicitly. If it does not match the returned type of the channel, a TypeError will be thrown at runtime. T cannot be a class with generics other than dynamic. For example, Map<String, String> is not supported but Map<dynamic, dynamic> or Map is.

Returns a Future which completes to one of the following:

  • a result (possibly null), on successful invocation;
  • a PlatformException, if the invocation failed in the platform plugin;
  • a MissingPluginException, if the method has not been implemented by a platform plugin.

The following code snippets demonstrate how to invoke platform methods in Dart using a MethodChannel and how to implement those methods in Java (for Android) and Objective-C (for iOS).

The code might be packaged up as a musical plugin, see flutter.dev/developing-packages/:
class Music {
  // Class cannot be instantiated.
  const Music._();

  static const MethodChannel _channel = MethodChannel('music');

  static Future<bool> isLicensed() async {
    // invokeMethod returns a Future<T?>, so we handle the case where
    // the return value is null by treating null as false.
    return _channel.invokeMethod<bool>('isLicensed').then<bool>((bool? value) => value ?? false);
  }

  static Future<List<Song>> songs() async {
    // invokeMethod here returns a Future<dynamic> that completes to a
    // List<dynamic> with Map<dynamic, dynamic> entries. Post-processing
    // code thus cannot assume e.g. List<Map<String, String>> even though
    // the actual values involved would support such a typed container.
    // The correct type cannot be inferred with any value of `T`.
    final List<dynamic>? songs = await _channel.invokeMethod<List<dynamic>>('getSongs');
    return songs?.cast<Map<String, Object?>>().map<Song>(Song.fromJson).toList() ?? <Song>[];
  }

  static Future<void> play(Song song, double volume) async {
    // Errors occurring on the platform side cause invokeMethod to throw
    // PlatformExceptions.
    try {
      return _channel.invokeMethod('play', <String, dynamic>{
        'song': song.id,
        'volume': volume,
      });
    } on PlatformException catch (e) {
      throw ArgumentError('Unable to play ${song.title}: ${e.message}');
    }
  }
}

class Song {
  Song(this.id, this.title, this.artist);

  final String id;
  final String title;
  final String artist;

  static Song fromJson(Map<String, Object?> json) {
    return Song(json['id']! as String, json['title']! as String, json['artist']! as String);
  }
}

Java (for Android):
// Assumes existence of an Android MusicApi.
public class MusicPlugin implements MethodCallHandler {
  @Override
  public void onMethodCall(MethodCall call, Result result) {
    switch (call.method) {
      case "isLicensed":
        result.success(MusicApi.checkLicense());
        break;
      case "getSongs":
        final List<MusicApi.Track> tracks = MusicApi.getTracks();
        final List<Object> json = ArrayList<>(tracks.size());
        for (MusicApi.Track track : tracks) {
          json.add(track.toJson()); // Map<String, Object> entries
        }
        result.success(json);
        break;
      case "play":
        final String song = call.argument("song");
        final double volume = call.argument("volume");
        try {
          MusicApi.playSongAtVolume(song, volume);
          result.success(null);
        } catch (MusicalException e) {
          result.error("playError", e.getMessage(), null);
        }
        break;
      default:
        result.notImplemented();
    }
  }
  // Other methods elided.
}

Objective-C (for iOS):
@interface MusicPlugin : NSObject<FlutterPlugin>
@end

// Assumes existence of an iOS Broadway Play Api.
@implementation MusicPlugin
- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
  if ([@"isLicensed" isEqualToString:call.method]) {
    result([NSNumber numberWithBool:[BWPlayApi isLicensed]]);
  } else if ([@"getSongs" isEqualToString:call.method]) {
    NSArray* items = [BWPlayApi items];
    NSMutableArray* json = [NSMutableArray arrayWithCapacity:items.count];
    for (final BWPlayItem* item in items) {
      [json addObject:@{ @"id":item.itemId, @"title":item.name, @"artist":item.artist }];
    }
    result(json);
  } else if ([@"play" isEqualToString:call.method]) {
    NSString* itemId = call.arguments[@"song"];
    NSNumber* volume = call.arguments[@"volume"];
    NSError* error = nil;
    BOOL success = [BWPlayApi playItem:itemId volume:volume.doubleValue error:&error];
    if (success) {
      result(nil);
    } else {
      result([FlutterError errorWithCode:[NSString stringWithFormat:@"Error %ld", error.code]
                                 message:error.domain
                                 details:error.localizedDescription]);
    }
  } else {
    result(FlutterMethodNotImplemented);
  }
}
// Other methods elided.
@end

See also:

Implementation

@optionalTypeArgs
Future<T?> invokeMethod<T>(String method, [ dynamic arguments ]) {
  return _invokeMethod<T>(method, missingOk: false, arguments: arguments);
}