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

# 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

```java
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

```objc
@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 

```dart
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

```java
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

```objc
@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 

```dart
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によって返された認証結果を処理する必要があります。

```java
  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アプリケーションから返された認証プロセスの結果を処理する必要があります。

```objc
@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\.Registrar](https://docs.flutter.io/javadoc/io/flutter/plugin/common/PluginRegistry.Registrar.html)と[FlutterPluginRegistrar](https://docs.flutter.io/objcdoc/Protocols/FlutterPluginRegistrar.html)を使用して解決します。

[PluginRegistry\.Registrar](https://docs.flutter.io/javadoc/io/flutter/plugin/common/PluginRegistry.Registrar.html)は、onActivityResultが実行されたときに呼ばれるActivityResultListenerを登録することが出来ます。
[FlutterPluginRegistrar](https://docs.flutter.io/objcdoc/Protocols/FlutterPluginRegistrar.html)は、AppDelegateを登録することが出来ます。

以下は、[flutter\_line\_login](https://github.com/granoeste/flutter_line_login)からの抜粋です。

```kotlin
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)
   }
```

```Objc
@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をリリースしています。

- [flutter\_line\_login \| Flutter Package](https://pub.dartlang.org/packages/flutter_line_login)
- [granoeste/flutter\_line\_login: A Flutter plugin for allowing users to authenticate with native Android & iOS LINE login SDKs\.](https://github.com/granoeste/flutter_line_login)

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