How to call native iOS function from Flutter using the Pigeon package?

681

I think the BinaryMessenger is getting set incorrectly.

I tried this and it worked: In the didFinishLaunchingWithOptions method get the rootViewController and cast it as FlutterBinaryMessenger:

let rootViewController : FlutterViewController = window?.rootViewController as! FlutterViewController
// get binaryMessenger
let binaryMessenger = rootViewController as! FlutterBinaryMessenger
    
// set binaryMessenger
ApiSetup(binaryMessenger, self)

The complete method becomes like this:

  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    GeneratedPluginRegistrant.register(with: self);
    
    let rootViewController : FlutterViewController = window?.rootViewController as! FlutterViewController
    
    // get binaryMessenger
    let binaryMessenger = rootViewController as! FlutterBinaryMessenger
    
    // set binaryMessenger
    ApiSetup(binaryMessenger, self)

    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
Share:
681
AbdulAziz Umar
Author by

AbdulAziz Umar

Updated on December 30, 2022

Comments

  • AbdulAziz Umar
    AbdulAziz Umar about 1 year

    I am trying to integrate some native code in my project. For that, I am using the new Pigeon package: https://pub.dev/packages/pigeon

    On Android it works just fine but on iOS, whenever I try to call the method written in swift (on button press), it throws a PlatformException(channel-error, Unable to establish connection on channel., null, null)

    I've setup the flutter starting project and then I am calling the native methods when the floatingActionButton is pressed. When the native method is executed, it returns a String saying "Hello from Android" on the android side and "Hello from Xcode" on iOS.

    main.dart file:

    import 'package:flutter/material.dart';
    import 'package:platforms/pigeon.dart';
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: MyHomePage(title: 'Flutter Demo Home Page'),
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      MyHomePage({Key? key, required this.title}) : super(key: key);
    
      final String title;
    
      @override
      _MyHomePageState createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      int _counter = 0;
    
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text(widget.title),
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Text(
                  'You have pushed the button this many times:',
                ),
                Text(
                  '$_counter',
                  style: Theme.of(context).textTheme.headline4,
                ),
              ],
            ),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: () async {
              Api api = Api();
              final searchReply = await api.search(SearchRequest());
              print(searchReply.result);
            },
            tooltip: 'Increment',
            child: Icon(Icons.add),
          ),
        );
      }
    }
    

    MainActivity.java file:

    package com.example.platforms;
    import android.os.Bundle;
    import io.flutter.embedding.android.FlutterActivity;
    
    public class MainActivity extends FlutterActivity {
        private class Api implements Pigeon.Api {
    
            @Override
            public Pigeon.SearchReply search(Pigeon.SearchRequest arg) {
                Pigeon.SearchReply searchReply = new Pigeon.SearchReply();
                searchReply.setResult("Hello from Android!");
                return searchReply;
            }
        }
    
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            Pigeon.Api.setup(getFlutterEngine().getDartExecutor().getBinaryMessenger(), new Api());
        }
        
    }
    

    AppDelegate.swift file:

    import UIKit
    import Flutter
    
    
    
    
    @UIApplicationMain
    @objc class AppDelegate: FlutterAppDelegate, Api {
        var engine: FlutterEngine = {
            let result = FlutterEngine.init()
            // This could be `run` earlier in the app to avoid the overhead of doing it the first time the
            // engine is needed.
            result.run()
            return result
          }()
        
        
        func search(_ input: SearchRequest, error: AutoreleasingUnsafeMutablePointer<FlutterError?>) -> SearchReply? {
            
            let reply = SearchReply()
            
            reply.result = "Hello from Xcode!!!!"
            return reply
        }
        
        
      override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
      ) -> Bool {
        
        GeneratedPluginRegistrant.register(with: self)
        ApiSetup(engine.binaryMessenger, self)
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
      }
    }
    

    Result of onPressed on Android: works as expected

    Result of onPressed on iOS: throws error

    What's also worth noting is that when I call the code in onPressed in the didChangedDependencies function, the code in swift does run, and the result does show up, but the same exception is then thrown again. Which means that the channel does get created, I am just using it wrong.

    When this is added to main.dart

    @override
      void didChangeDependencies() async {
        Api api = Api();
        final searchReply = await api.search(SearchRequest());
        print(searchReply.result);
        super.didChangeDependencies();
      }
    

    prints result before throwing error

    It prints the result from swift and then throws the same error.