Skip to main content

Mobile

Make Your Flutter Apps Soar with Pigeon Platform Channels

Mobile Bu Pigeon Flutter

Flutter is great framework for cross platform development. It allows you to make pixel perfect apps that are generated into native code, but what happens if you need to use existing code in iOS or Android directly? For situations like these, Flutter allows you to use platform channels.

Platform channels give you access to platform-specific APIs in a language that works directly with those APIs. Platform channels are available for Kotlin or Java on Android, Swift or Objective-C on iOS and macOS, C++ on Windows and C on Linux.

More information can be found on this here https://docs.flutter.dev/platform-integration/platform-channels

The platform APIs provided by Flutter work as intended, but the whole process is a bit cumbersome to set up. Pigeon allows us to use type safety and code generation to make this process a whole lot simpler.

Create a Pigeon Plugin

We will go ahead and create a simple example api.

Let’s start by creating a new plugin called pigeon_example

flutter create --org com.example --template=plugin --platforms=android,ios,linux,macos,windows -i swift pigeon_example
flutter pub add pigeon
flutter pub get

Platform Channel Types in Swift

Below is a list of supported types in Dart and their swift equivalents. We will use some of the most common types in our example

Dart TypesSwift Types
nullnil
boolNSNumber(value: Bool)
intNSNumber(value: Int32)
int, if 32 bits not enoughNSNumber(value: Int)
doubleNSNumber(value: Double)
StringString
Uint8ListFlutterStandardTypedData(bytes: Data)
Int32ListFlutterStandardTypedData(int32: Data)
Int64ListFlutterStandardTypedData(int64: Data)
Float32ListFlutterStandardTypedData(float32: Data)
Float64ListFlutterStandardTypedData(float64: Data)
ListArray
MapDictionary

Define Our API

In order to let Pigeon know what methods we’re going to be exposing we define our API in an abstract Dart class with the @HostApi() decorator, and its methods

Let’s define our Pigeon Example API in a new directory named pigeons.

import 'package:pigeon/pigeon.dart';

@HostApi()

abstract class ExampleApi {
bool getBool();
String getString();
func toggleValue();
}

Generate Pigeon Platform Code

Now we can let the Pigeon package do it’s magic and we can generate some code

dart run pigeon \
--input pigeons/example_api.dart \
--dart_out lib/example_api.dart \
--experimental_swift_out ios/Classes/ExampleApi.swift \
--kotlin_out ./android/app/src/main/kotlin/com/example/ExampleApi.kt \
--java_package "io.flutter.plugins"

Be sure that the paths to all of the files are correct or the next steps won’t work. Generate the code with the output for the platforms needed. This is example is going to focus on using Swift.

Add Method Implementation to the Runner

Next we need to write our native implementation of our methods. When doing this we need to add our files to the runner in Xcode to ensure that they run properly.

class ExampleApiImpl : ExampleApi{
var value = true;

func getBool(){
return value;
}
func toggleValue(){
    value = !value
  }
func getString(){
return "THIS IS AN EXAMPLE";
}

}

Add Pigeon Platform Channel to AppDelegate

You will also need to add this code in your AppDelegate.swift file

@UIApplicationMain

@objc class AppDelegate: FlutterAppDelegate {

override func application(

_ application: UIApplication,

didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?

) -> Bool {

GeneratedPluginRegistrant.register(with: self)

let exampleApi = ExampleApiImpl()

let controller : FlutterViewController = window?.rootViewController as! FlutterViewController

ExampleApiSetup.setUp(binaryMessenger: controller.binaryMessenger, api: exampleApi)




return super.application(application, didFinishLaunchingWithOptions: launchOptions)

}

}

 

Now you should be able to use your API in Dart code.

 

import 'package:flutter/material.dart';
import 'package:pigeon_example/example_api.dart';
import 'dart:async';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final exampleApi = ExampleApi();
  bool value = false;
  @override
  void initState() {
    super.initState();
    initPlatformState();
  }

  // Platform messages are asynchronous, so we initialize in an async method.
  Future<void> initPlatformState() async {
    // Platform messages may fail, so we use a try/catch PlatformException.
    // We also handle the message potentially returning null.
    // If the widget was removed from the tree while the asynchronous platform
    // message was in flight, we want to discard the reply rather than calling
    // setState to update our non-existent appearance.
    if (!mounted) return;
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Scaffold(
            appBar: AppBar(
              title: const Text('Plugin example app'),
            ),
            body:
                Column(mainAxisAlignment: MainAxisAlignment.center, children: [
              DefaultTextStyle(
                  style: Theme.of(context).textTheme.displayMedium!,
                  textAlign: TextAlign.center,
                  child: FutureBuilder<String>(
                    future: exampleApi
                        .getString(), // a previously-obtained Future<String> or null
                    builder:
                        (BuildContext context, AsyncSnapshot<String> snapshot) {
                      List<Widget> children = [];
                      if (snapshot.data!.isNotEmpty) {
                        children = <Widget>[
                          Text(snapshot.data ?? ''),
                        ];
                      }
                      return Center(
                        child: Column(
                          mainAxisAlignment: MainAxisAlignment.center,
                          children: children,
                        ),
                      );
                    },
                  )),
              Center(
                child: ElevatedButton(
                  child: const Text('Toggle Value'),
                  onPressed: () async {
                    await exampleApi.toggleValue();
                    var val = await exampleApi.getBool();
                    setState(() {
                      value = val;
                    });
                  },
                ),
              ),
              DefaultTextStyle(
                  style: Theme.of(context).textTheme.displayMedium!,
                  textAlign: TextAlign.center,
                  child: FutureBuilder<bool>(
                    future: exampleApi
                        .getBool(), // a previously-obtained Future<String> or null
                    builder:
                        (BuildContext context, AsyncSnapshot<bool> snapshot) {
                      List<Widget> children;
                      if (snapshot.data == true) {
                        children = <Widget>[
                          const Icon(
                            Icons.check_circle_outline,
                            color: Colors.green,
                            size: 60,
                          ),
                          Padding(
                            padding: const EdgeInsets.only(top: 16),
                            child: Text('Result: ${snapshot.data}'),
                          ),
                        ];
                      } else if (snapshot.data == false) {
                        children = <Widget>[
                          const Icon(
                            Icons.error_outline,
                            color: Colors.red,
                            size: 60,
                          ),
                          Padding(
                            padding: const EdgeInsets.only(top: 16),
                            child: Text('Result: ${snapshot.data}'),
                          ),
                        ];
                      } else {
                        children = const <Widget>[
                          SizedBox(
                            width: 60,
                            height: 60,
                            child: CircularProgressIndicator(),
                          ),
                          Padding(
                            padding: EdgeInsets.only(top: 16),
                            child: Text('Awaiting result...'),
                          ),
                        ];
                      }
                      return Center(
                        child: Column(
                          mainAxisAlignment: MainAxisAlignment.center,
                          children: children,
                        ),
                      );
                    },
                  ))
            ])));
  }
}

 

Now we can see the values from out example API in our Flutter UI. Button toggles will change our boolean value.

Simulator Screenshot Iphone 12 2024 03 21 At 12.56.11 Simulator Screenshot Iphone 12 2024 03 21 At 12.56.08

You can follow this same pattern for any type of data supported by Pigeon.

For more information about Perficient’s Mobile Solutions expertise, subscribe to our blog or contact our Mobile Solutions team today!

 

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Josh Blankenship

Joshua Blankenship is a Lead Technical Consultant on the Mobile Solutions team. He specializes in cross-platform development and offers perspectives on native and mobile integrations. Joshua enjoys staying up to date on the latest mobile technologies.

More from this Author

Follow Us
TwitterLinkedinFacebookYoutubeInstagram