[Flutter] URIをネイティブからFlutter側に渡すためのベストプラクティス (多分)

2023/08/25 17:23公開
2023/08/25 19:14最終更新
Table of Contents
  1. 前提
  2. ホスト(ネイティブ)側のコード
    1. iOS
    2. Android
  3. Flutter側のコード

前提

ネイティブからFlutterにURIを渡すためには、EventChannelを利用すると思います。単純にFlutter側でmain()でリスナーを設定し、ネイティブでapplication(_:open:options:)onNewIntentなどでURIを送り込むと、アプリ起動中にURIが流れてきた場合は動作するしれませんが、URIによってアプリが起動された際にはURIを渡すことができません。何故なら、Flutterエンジンの初期化には時間がかかるためです。

そのため、リッスンされるまではネイティブのコードでURIを貯蔵しておき、リッスンが開始された際に流し込む、というようなアプローチが必要になります。

ホスト(ネイティブ)側のコード

iOS

class UriEventApi: NSObject {
    static let channelName = "com.example.app.event/uri" // Channel name (任意に変更)
    var channel: FlutterEventChannel
    var eventSink: FlutterEventSink? // リッスンが開始されたタイミングで代入され、キャンセルされたタイミングでnilにされる
    var pendingUri: String?
    
    init(binaryMessenger: FlutterBinaryMessenger) {
        channel = FlutterEventChannel(name: UriEventApi.channelName, binaryMessenger: binaryMessenger)
    }
    
    func initHandler() {
        channel.setStreamHandler(self)
    }
    
    func onUri(uri: String) {
        if (eventSink != nil) { // Flutter側で既にリッスンが開始されている場合、
            eventSink!(uri) // そのままeventSinkを発火させる
        } else { // リッスンが開始されていない = まだFlutterエンジンが初期化されていない 場合、
            pendingUri = uri // pendingUriに代入して貯めておく
        }
    }
}

extension UriEventApi: FlutterStreamHandler {
    func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { // リッスンが開始された際、
        eventSink = events
        if (pendingUri != nil) { // 保留中のURIが存在する場合、
            eventSink!(pendingUri) // eventSinkを発火させてFlutter側に伝える
            pendingUri = nil
        }
        return nil
    }
    func onCancel(withArguments arguments: Any?) -> FlutterError? {
        eventSink = nil
        return nil
    }
}

Android

// 実装の内容はiOS(Swift)と同様

class UriEventApi(binaryMessenger: BinaryMessenger) : EventChannel.StreamHandler {
    private val channel = EventChannel(binaryMessenger, CHANNEL_NAME)

    companion object {
        const val CHANNEL_NAME = "com.example.event/uri"
    }

    private var eventSink: EventChannel.EventSink? = null
    private var pendingData: String? = null

    fun initHandler() {
        channel.setStreamHandler(this)
    }

    fun onUri(uri: String) {
        if (eventSink == null) {
            pendingData = uri
        } else {
            eventSink!!.success(uri)
        }
    }

    override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
        eventSink = events
        if (pendingData != null) {
            eventSink!!.success(pendingData)
            pendingData = null
        }
    }

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

Flutter側のコード

扱いやすくするためにUriEventApiという名前でクラスを作ります。

// lib/event_api/uri_event_api.dart

class UriEventApi {
  static const _eventName = "com.example.app.event/uri";
  static const _channel = EventChannel(_eventName);

  StreamSubscription listen() {
    return _channel.receiveBroadcastStream().listen((uri) {
      // process
    });
  }
}

これを任意のタイミングでリッスンします。

UriEventApi().listen();

このコードで基本的には動くはずです。