Edited at

How to handle 3rd-Party Apps in a plugin packages in Flutter

More than 1 year has passed since last update.


What's a plugin package?

プラットフォーム固有のAPIを呼び出すパッケージを開発する場合は、プラグインパッケージを開発する必要があります。

プラットフォーム固有のAPIとの通信では、MethodCannelとEventChannelが使用されます。


Communication channels to platform on a plugin package


MethodCannel

MethodCannelは、ホストのメソッドを呼び出してプラットフォームのAPIを実行し、結果を戻り値として受け取ります。

 Flutter                      Host <iOS>

MethodCannel ┌───────────────┐
┌──────>│ FlutterPlugin │
│ └───────────────┘
┌──────────┐ Call │
│ Plugin │*─────────┤
│ Packages │ │ Host <Android>
└──────────┘ │ ┌────────────────┐
└──────>│ FlutterPlugin? │
└────────────────┘


実装の手順


  1. MethodChannelを作成

  2. MethodChannelにPluginを登録

  3. FlutterからのMethodCall を受け取る

  4. Flutterへ結果をコールバックする


Android

public class HelloPlugin implements MethodCallHandler {

// static method.
public static void registerWith(Registrar registrar) {
// 1. MethodChannelを作成
final MethodChannel channel = new MethodChannel(registrar.messenger(), "hello");
// 2. MethodChannelにPluginを登録
HelloPlugin instance = new HelloPlugin();
channel.setMethodCallHandler(instance);
}

@Override
public void onMethodCall(MethodCall call, Result result) {
// 3. FlutterからのMethodCallを受け取る
if (call.method.equals("getPlatformVersion")) {
// 4. Flutterへ結果をコールバックする
if (/*condition*/) {
// 4-1. Success
result.success("Android " + android.os.Build.VERSION.RELEASE);
} else {
// 4-2. Error
result.error("ERROR", "errorMessage", null):
}
} else {
// 4-2. NotImplemented Error
result.notImplemented();
}
}
}


  • スタティックメソッドのシネグチャは public static void registerWith(Registrar registrar) と一致させる必要があります。


Method of the Result for callback.


  • success(Object result) ... 成功した結果を処理します。

  • error(String errorCode, String errorMessage, Object errorDetails) ... エラーの結果を処理します。

  • notImplemented() ... 実装されていないメソッドへの呼び出しを処理します。


iOS

@interface HelloPlugin : NSObject<FlutterPlugin>

@end

@implementation HelloPlugin
// Implemented by the iOS part of a FlutterPlugin.
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
// 1. MethodChannelを作成
FlutterMethodChannel* channel = [FlutterMethodChannel
methodChannelWithName:@"hello"
binaryMessenger:[registrar messenger]];
// 2. MethodChannelにPluginを登録
HelloPlugin* instance = [[HelloPlugin alloc] init];
[registrar addMethodCallDelegate:instance channel:channel];
}

- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
// 3. FlutterからのMethodCallを受け取る
if ([@"getPlatformVersion" isEqualToString:call.method]) {
// 4. Flutterへ結果をコールバックする
if (/*condition*/) {
// 4-1. Success
result([@"iOS " stringByAppendingString:[[UIDevice currentDevice] systemVersion]]);
} else {
// 4-2. Error
result([FlutterError errorWithCode:@"ERROR"
message:"errorMessage"
details:nil]);
}

} else {
// 4-2. NotImplemented Error
result(FlutterMethodNotImplemented);
}
}

@end

FlutterResult ... メソッド呼び出しの結果をFlutter呼び出し元に戻すために使用されます。

ErrorまたはnotImplementedをコールバックすると、FlutterはPlatformExceptionを発生させるので、try-catchする必要があります。


Flutter

class Hello {

// 1. MethodChannelを作成
static const MethodChannel _channel = const MethodChannel('hello');

static Future<String> get platformVersion async {
// Platform messages may fail, so we use a try/catch PlatformException.
try {
// 2. メソッドの呼び出し
final String version = await _channel.invokeMethod('getPlatformVersion');
return version;
} on PlatformException catch (e) {
debugPrint(${e});
return null;
}
}
}


EventChannel

EventChannelは、ブロードキャストストームの受信者をホストに登録し、コールバックで非同期にプラットフォームイベントを受信します。

                               Host <iOS>

EventChannel ┌───────────────┐
┌──────*│ FlutterPlugin │
Flutter │ └───────────────┘
┌──────────┐ Callback │
│ Plugin │<─────────┤
│ Packages │ │ Host <Android>
└──────────┘ │ ┌────────────────┐
└──────*│ FlutterPlugin? │
└────────────────┘


実装の手順


  1. StreamHandlerのメソッドを実装

  2. EventChannelの作成

  3. EventChannelにPluginを登録

  4. イベントストリームを設定

  5. イベントをFlutterにエミット


Android

public class HelloPlugin implements EventChannel.StreamHandler {

private EventChannel.EventSink _eventSink;

public static void registerWith(Registrar registrar) {
// 2. EventChannelの作成
final EventChannel eventChannel = EventChannel(registrar.messenger(), "hello");
// 3. EventChannelにPluginを登録
HelloPlugin instance = new HelloPlugin();
eventChannel.setStreamHandler(instance);
}

// 1. StreamHandlerのメソッドを実装
/**
* イベントストリームの設定する要求を処理
*
* @arguments possibly null.
* @events Flutterレシーバにイベントを送信するEventChannel.EventSink。
*/

@Override
public void onListen(Object arguments, EventChannel.EventSink events) {
// 4. イベントストリームを設定
_eventSink = events;
}

/**
* イベントストリームを破棄する要求を処理
*
* @arguments possibly null.
*/

@Override
public void onCancel(Object arguments) {
_eventSink = null;
}

final Handler handler = new Handler();
final Runnable runnable = new Runnable() {
@Override
public void run() {
if(_eventSink != null) {
// 5. イベントをFlutterにエミット
if (/*condition*/) {
// 5-1. Success
_eventSink.success("Hello");
} else {
// 5-2. Error
_eventSink.error("ERROR", "errorMessage", null);
}
}
handler.postDelayed(this, 10000);
}
};
handler.post(runnable);

}


  • スタティックメソッドのシネグチャは public static void registerWith(Registrar registrar) と一致させる必要があります。


iOS

@interface HelloPlugin : NSObject<FlutterPlugin, FlutterStreamHandler>

@end

// Implemented by the iOS part of a FlutterPlugin and FlutterStreamHandler.
@implementation HelloPlugin
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
// 2. EventChannelの作成
FlutterEventChannel* channel = [FlutterEventChannel
eventChannelWithName:@"hello"
binaryMessenger:[registrar messenger]];
// 3. EventChannelにPluginを登録
HelloPlugin* instance = [[HelloPlugin alloc] init];
[registrar addMethodCallDelegate:instance channel:methodChannel];
}

// 1. StreamHandlerのメソッドを実装
/**
イベントストリームの設定する要求を処理

- Parameters:
- arguments: Arguments for the stream.
- events: Flutterレシーバにイベントを送信するEventSink。
- Returns: A FlutterError instance, if setup fails.
*/
- (FlutterError *)onListenWithArguments:(id)arguments eventSink:(FlutterEventSink)eventSink {
// 4. イベントストリームを設定
_eventSink = eventSink;
return nil;
}

/**
イベントストリームを破棄する要求を処理

- Parameters
- arguments: Arguments for the stream.
- Returns: A FlutterError instance, if teardown fails.
*/
- (FlutterError *)onCancelWithArguments:(id)arguments {
_eventSink = nil;
return nil;
}

- (void)emit {
[[[NSOperationQueue alloc] init] addOperationWithBlock:^{
if(_eventSink != null) {
// 5. イベントをFlutterにエミット
if (/*condition*/) {
// 5-1. Success
_eventSink("Hello");
} else {
// 5-2. Error
_eventSink([FlutterError errorWithCode:@"ERROR"
message:"errorMessage"
details:nil]);
}
}
}];
}
@end


Flutter

class Hello {

// 1. EventChannelの作成
static const EventChannel _channel = const EventChannel('hello');

Hello() {
// 2. ブロードキャストストリームのリスナーを設定
_eventChannel.receiveBroadcastStream().listen(_onEvent, onError: _onError);
}

// 3. イベントストリームの処理
void _onEvent(Object event) {
debugPrint(${event});
}

// 4. エラーストリームの処理
void _onError(Object error) {
debugPrint(${error});
}
}


How to handle 3rd-Party Apps in a plugin packages

一般的なアプリケーションでは、MainActivityまたはAppDelegate/ViewControllerから3rd-Partyアプリケーションを呼び出すことができます。

以下は、LINEアプリケーションのSDKを使用したログイン処理の例です。

Androidの場合、startActivityForResultを使用してLINEアプリケーションを呼び出し、onActivityResultによって返された認証結果を処理する必要があります。

  public void login() {

try{
// App-to-app login
Intent loginIntent = LineLoginApi.getLoginIntent(v.getContext(), CHANNEL_ID);
startActivityForResult(loginIntent, REQUEST_CODE);

} catch(Exception e) {
Log.e("ERROR", e.toString());
}
}

public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);

LineLoginResult result = LineLoginApi.getLoginResultFromIntent(data);
switch (result.getResponseCode()) {
case SUCCESS:
// Login successful
String accessToken = result.getLineCredential().getAccessToken().getAccessToken();
// ...
break;

case CANCEL:
// Login canceled by user
break;

}
}

iOSの場合、AppDelegateのLINEアプリケーションから返された認証プロセスの結果を処理する必要があります。

@implementation AppDelegate

- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options
{
return [[LineSDKLogin sharedInstance] handleOpenURL:url];
}

@end


PluginRegistry.Registrar or FlutterPluginRegistrar

プラグインパッケージは、MainActivityやAppDelegate/ViewControllerを持っていませんので、

PluginRegistry.RegistrarFlutterPluginRegistrarを使用して解決します。

PluginRegistry.Registrarは、onActivityResultが実行されたときに呼ばれるActivityResultListenerを登録することが出来ます。

FlutterPluginRegistrarは、AppDelegateを登録することが出来ます。

以下は、flutter_line_loginからの抜粋です。

class FlutterLineLoginPlugin(registrar: Registrar) : MethodCallHandler, EventChannel.StreamHandler, PluginRegistry.ActivityResultListener {

companion object {

@JvmStatic
fun registerWith(registrar: Registrar): Unit {
FlutterLineLoginPlugin(registrar)
}
}

init {
// 1. MethodChannelの作成
val methodChannel = MethodChannel(registrar.messenger(), "net.granoeste/flutter_line_login")
// 2. EventChannelの作成
val eventChannel = EventChannel(registrar.messenger(), "net.granoeste/flutter_line_login_result")

// 3. MethodChannelとEventChannelにPluginを登録
methodChannel.setMethodCallHandler(this)
eventChannel.setStreamHandler(this)

// 4. ActivityResultListenerにPluginを登録
registrar.addActivityResultListener(this)
}

@interface FlutterLineLoginPlugin : NSObject<FlutterPlugin, LineSDKLoginDelegate, FlutterStreamHandler>

@end

@implementation FlutterLineLoginPlugin {
LineSDKAPI *apiClient;
}

+ (void)registerWithRegistrar:(NSObject <FlutterPluginRegistrar> *)registrar {

// 1. MethodChannelの作成
FlutterMethodChannel *methodChannel =
[FlutterMethodChannel methodChannelWithName:@"net.granoeste/flutter_line_login"
binaryMessenger:[registrar messenger]];
// 2. EventChannelの作成
FlutterEventChannel *eventChannel =
[FlutterEventChannel eventChannelWithName:@"net.granoeste/flutter_line_login_result"
binaryMessenger:[registrar messenger]];
// 3. MethodChannelとEventChannelにPluginを登録
FlutterLineLoginPlugin *instance = [[FlutterLineLoginPlugin alloc] init];
[registrar addMethodCallDelegate:instance channel:methodChannel];
[eventChannel setStreamHandler:instance];

// 4. PluginをAppDelegateに登録
[registrar addApplicationDelegate:instance];

}

これで3rd-Partyアプリを処理することができます。

これらのテクニックを使って、Flutter LINE login packageをリリースしています。

よかったら使ってください。