Platform-Specific Code With Flutter Method Channel: Getting Started

Learn how to communicate with some platform-specific code with Flutter method channels and extend the functionality of the Flutter application. By Wilberforce Uwadiegwu.

Leave a rating/review
Download materials
Save for later
Share

The concept of cross-platform messaging was introduced in an earlier tuorial. That tutorial briefly highlighted the difference between the Event Channel and Method Channel. It went into detail on how to stream data from the host platform to your Dart code. This tutorial focuses on cross-platform messaging with the Method Channel. Here, you’ll build Photos Keyboard, an app that replicates the behavior of Slack’s image picker. You’ll learn how to:

  • Invoke methods on host platforms and listen for method calls.
  • Query images from the user’s photo library.
  • Build a custom image provider.
Note: This tutorial assumes that you have some experience with Flutter and Flutter widgets and is the continuation of An In-Depth Dive Into Streaming Data Across Platform Channels on Flutter tutorial.

Getting Started

Download the project by clicking Download Materials at the top or bottom of this tutorial. Unzip the file and open the starter folder with the latest version of Android Studio or Visual Studio Code. The project has two directories: common and widgets. The common directory contains code used in multiple files, and the widget directory houses widget files.

Open pubspec.yaml and click the Pub get tab that appears in your IDE. Open lib/main.dart and run the project to see this on your target emulator or device:

Screenshot of the empty starter Platfrom-Specific Code project

This tutorial is divided into three main sections.

  • The first section walks you through setting up Method Channel on iOS, listening for method calls, querying the OS for photos and returning the results to Flutter.
  • In the second section, you’ll complete the UI to render the images and invoke methods on the host platform.
  • Finally, using Kotlin on Android, the last section guides you in setting up and listening for method calls on the Method Channel, retrieving images from the gallery and sending the decoded image bytes back to Flutter for rendering.

Understanding the Method Channel

The Method Channel stems from binary messaging and the platform channel. The previously mentioned tutorial on Event Channel provided an overview of these APIs and explained how the Method Channel and Event Channel are types of platform channels. Here’s an illustration of the Platform Channel stack:

The stack of Platform Channel

An illustration of the Platform Channel stack

The Method Channel, unlike the Event Channel, supports a bidirectional invocation of methods. An interesting thing to keep in mind here is that the method invocation isn’t technically an “invocation”, as the API doesn’t call the function for you. As you’ll see later, when the method is received, you can check the method invoked and call the function yourself. Mikkel Ravn wrote a detailed article on Platform Channels and the design tradeoffs.

Setting up Method Channel on iOS With Swift

To receive method calls on iOS, you need the channel’s name and a closure. The name is like an ID for the channel and must be the same for iOS, Flutter and Android. The Flutter Platform Engine calls the closure when you invoke a method on the Method Channel from the Dart side. In this closure, you retrieve the name and parameters of the invoked method and consume it.

Start by opening Runner.xcworkspace in the starter/ios directory with Xcode. Then, declare the following variables above application() inside the AppDelegate class in AppDelegate.swift:

 // 1
 private var flutterResult: FlutterResult? = nil
 // 2
 private var fetchLimit: Int = 0
 // 3
 private var fetchResult: PHFetchResult<PHAsset>? = nil

The use case for the variables above is as follows:

  1. Handles communication back to Flutter.
  2. Maximum number of photos to fetch.
  3. Reference to the photo query result.

Still in AppDelegate.swift, add this import statement below the other import statements:

import Photos

Next, add the following code above GeneratedPluginRegistrant.register(with: self) inside application():

let controller = (window?.rootViewController as! FlutterViewController)
// 1
let methodChannel = 
    FlutterMethodChannel(name: "com.raywenderlich.photos_keyboard", binaryMessenger: controller.binaryMessenger)
// 2
methodChannel
    .setMethodCallHandler({ [weak self](call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
    switch call.method {
    case "getPhotos":
        // 3
        self?.fetchLimit = call.arguments as! Int
        self?.flutterResult = result
        self?.getPhotos()
    default:
        // 4
        result(FlutterMethodNotImplemented)
    }
})

Here’s what the code above does:

  1. Initializes the channel with the name com.raywenderlich.photos_keyboard.
  2. Listens for incoming function calls.
  3. Loosely translated, this means “call getPhotos() when Dart side invokes getPhotos“.
  4. Handles unknown methods.

Understanding PhotoKit

PhotoKit comprises APIs that sit between your app and local and iCloud media. These APIs essentially control access to media in the Photos app, dictating how your app reads and writes from them. Throughout this tutorial, you’ll interact with some of these APIs, like PHPhotoLibrary, PHAsset and PHImageManager.

Note: If you have some iOS experience and you are interested in learning more about PhotoKit, check out our Getting Started with PhotoKit tutorial

Requesting Permissions on iOS

On iOS, sensitive user data like the photos library is walled behind protected APIs like PhotoKit. You add a key-value pair to Info.plist to declare your app’s intention of accessing that data. The key is the ID for that data and the value is the reason your app needs the data.

Now, open Info.plist. Hover on the rows of keys on the left, and click any of the + icons that appear. Start typing “Privacy” and select Privacy – Photo Library Usage Description from the drop-down. Then, paste “Photos Keyboard needs access to photos to make them available for selection” for the value column for that same row.

Selecting Privacy - Photo Library Usage Description and pasting a text string

Next, request permission from the user when Flutter calls getPhotos. Write this function below application():

private func getPhotos() {
    PHPhotoLibrary.requestAuthorization { (status) in
        if (status == .authorized) {
        } else {
            let error =
                FlutterError
                .init(code: "0", message: "Not authorized", details: status)
            self.flutterResult?(error)
        }
    }
}

PHPhotoLibrary.requestAuthorization() displays a permission prompt to the user and status is the the reponse of the user.

Fetching all Photos From iOS

Now that you have the user’s blessing to access their photos, read the photos and return the result to Flutter. Add the following code inside the if branch of the previous statement:

let options = PHFetchOptions()
options.fetchLimit = self.fetchLimit
options.sortDescriptors = 
    [NSSortDescriptor(key: "creationDate", ascending: false)]
self.fetchResult = PHAsset.fetchAssets(with: .image, options: options)

var results: [String] = []
self.fetchResult?.enumerateObjects { asset, count, stop in
    results.append(asset.localIdentifier)
}
self.flutterResult?(results)

This fetches the images, sorting them by creation date, and picks out the IDs of the results.