概要

Flutterアプリを作成している際、ネイティブコードを使いたい時があります。
ここではAndroidでの実装方法を2パターンご紹介します。
外部パッケージの導入は保守や脆弱性対応の対象も増えます。
実装量もそれほど多くないため、選択肢として把握しておくと便利です。

パターン1 Flutterからネイティブコードを呼ぶ

Flutterからネイティブコードを呼ぶ場合、MethodChannelを使用します。
Android側で入り口(MethodChannel)を名前付きで用意しておき、名前を元にFlutter側で呼ぶイメージです。

Android側

import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
class MainActivity(): FlutterActivity() {
    
    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "jp.co.jwork.sample.app/sample")
            .setMethodCallHandler { methodCall: MethodCall, result: MethodChannel.Result ->
            if (methodCall.method == "input") {
                val args: List<Any> = methodCall.arguments as List<Any>
                // ... Androidでやりたい処理 ...
                result.success("ok") // "ok"部がレスポンスになります。エラーの場合、result.errorも使えます。
            } else {
                result.notImplemented()
            }
        }
    }
}

Flutter側

import 'dart:async';

import "package:flutter/services.dart";

class Caller {

    static const MethodChannel _methodChannel = const MethodChannel('jp.co.jwork.sample.app/sample');

    Future run(String param) async {
        try {
            return await _methodChannel.invokeMethod("input", [ param ]);
        } on PlatformException {
            // エラーハンドリング
        }
    }
}

パターン2 AndroidからイベントをFlutter側に流す

AndroidからFlutterへ渡す場合、EventChannelを使用できます。
Flutter側で入り口(EventChannel)を名前付きで用意しておき、名前を元にAndroid側で呼ぶイメージです。
なお、1回きりで良い場合は、同じ流れでパターン1のMethodChannelを使うことでも実現可能です。

Android側

import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.EventChannel
class MainActivity(): FlutterActivity() {
    
    private var mInputEventSink: EventChannel.EventSink? = null

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        EventChannel(flutterEngine.dartExecutor.binaryMessenger, "jp.co.jwork.sample.app/sampleEvent")
            .setStreamHandler(object : EventChannel.StreamHandler {
                override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
                    mInputEventSink = events
                }

                override fun onCancel(arguments: Any?) {
                    mInputEventSink = null
                }
        })
    }

    // 何らかのAndroid側処理の値
    private fun onInput(value: String) {
        mInputEventSink?.success(value)
    }
}

Flutter側

import 'dart:async';

import "package:flutter/services.dart";

class Receiver {

    static const EventChannel _eventChannel = const EventChannel("jp.co.jwork.sample.app/sampleEvent");
    
    StreamSubscription? _listenEventStreamSubs;
    
    // 引数で関数を受けていますが、単純にStreamSubscriptionを返す作りでも良いと思います。
    Future<void> listen(void Function(String value) onListen) async {
        await _listenEventStreamSubs?.cancel();
        _listenEventStreamSubs = null;
        _listenEventStreamSubs = _eventChannel.receiveBroadcastStream().listen((event) {
            if (event is String) {
                onListen(event);
            }
        }, onError: (error) {
            // エラーハンドリング
        }, cancelOnError: true);
    }

    // 不要になったら閉じます。
    Future<void> cancel() async {
        await _listenEventStreamSubs?.cancel();
        _listenEventStreamSubs = null;
    }
}

まとめ

MethodChannel、EventChannelで簡単にAndroidコードとやりとりが出来ました。
汎用的なものならpluginとして切り出し、使いまわせるようにするのも良いと思います。