Your Own Image Picker With Flutter Channels

In this tutorial you will learn how to use Flutter Channels to communicate with platform code and create an image picker for both Android and iOS. By JB Lorenzo.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 2 of this article. Click here to view the first page.

Setting Up Platform Channels

To communicate between platform code and Flutter, you need to open a channel on both sides. Here, you will use a method channel.

Platform Channels in Flutter

In Android Studio, open lib/MultiGallerySelectPage.dart. Add this code inside the _MultiGallerySelectPageState class:

// import should be on top
import 'package:flutter/services.dart';

// This should be inside the state class
final _channel = MethodChannel("/gallery");

This opens a channel with the specified name, /gallery, from Flutter. Then you should try to invoke a method in the channel so that you can see the real image count from your gallery. Add the following code in initState():

_channel.invokeMethod<int>("getItemCount").then((count) => setState(() {
  _numberOfItems = count;
}));

This calls a getItemCount method in the channel. When it receives the response, it will set the variable _numberOfItems using setState. With setState, the Flutter UI is redrawn/refreshed.

You should then open a channel with the same name in Android and iOS.

Platform Channels in Android

For Android, open MainActivity.kt. Then add the following just after the call to GeneratedPluginRegistrant.registerWith(this) within the onCreate function, and be sure to add the import shown to the top of the file:

import io.flutter.plugin.common.MethodChannel

// 1
val channel = MethodChannel(flutterView, "/gallery")

// 2
channel.setMethodCallHandler { call, result ->
  when (call.method) {
    // 3
    "getItemCount" -> result.success(getGalleryImageCount())
    else -> println("unhandled")
  }
}

Here, you did the following:

  1. Open a channel with the same name that you previously used (/gallery).
  2. Set a handler for the channel.
  3. If the method is named getItemCount, then provide the image count to the success function.

You will now do the same thing for iOS.

Platform Channels in iOS

Open AppDelegate.swift and add the following just after GeneratedPluginRegistrant.register(with: self):

// 1
guard let controller = window?.rootViewController as? FlutterViewController else {
  fatalError("rootViewController is not type FlutterViewController")
}

// 2
let channel = FlutterMethodChannel(name: "/gallery", binaryMessenger: controller)

// 3
channel.setMethodCallHandler { (call, result) in
  switch (call.method) {
  // 4
    case "getItemCount": result(self.getGalleryImageCount())
    default: result(FlutterError(code: "0", message: nil, details: nil))
  }
}

Similar to Android, you do the following:

  1. Get the view controller instance so that you can open a channel.
  2. Open a channel with the same name that you previously used (/gallery).
  3. Set a handler for the channel.
  4. If the method is named getItemCount, then provide the image count to the result function. Also, there is a default switch handler for completeness.

At this point, you should be able to see the actual count of images in each platform. Build and run the project and you should see this result in the app.

Encoding the Gallery Data for Flutter

You already are providing the total image count to Flutter. Now it’s time to provide the actual image data.

In Android Studio, open MainActivity.kt. Then, insert the following inside the when (call.method) { block. It can come before or after getItemCount:

// 1
"getItem" -> {
  // 2
  val index = (call.arguments as? Int) ?: 0
  // 3
  dataForGalleryItem(index) { data, id, created, location ->
    // 4
    result.success(mapOf<String, Any>(
        "data" to data,
        "id" to id,
        "created" to created,
        "location" to location
    ))
  }
}

Going over each, in turn:

  1. Here, you add a case to when. You perform this case when the call method is getItem.
  2. Parse the call arguments. You are assuming that the argument is an integer.
  3. Get the associated data for this index. Call the dataForGalleryItem method that you made earlier.
  4. Provide a map containing the keys and associated values to the success function.

Next, you will do the same for iOS. Open AppDelegate.swift and insert these lines into the switch (call.method) section. It can be placed before or after the case for getItemCount:

// 1
case "getItem":
  // 2
  let index = call.arguments as? Int ?? 0
  // 3
  self.dataForGalleryItem(index: index, completion: { (data, id, created, location) in
    // 4
    result([
         "data": data ?? Data(),
         "id": id,
         "created": created,
         "location": location
    ])
  })

You may have noticed that this is very similar to the previous code, except this time for iOS and Swift. The description for each number is the same.

There! You have encoded the data for the channel! :]

This section has no visual difference from the previous one. Build and run just to make sure the project compiles. You will see the result after the next section.

Decoding the Gallery Data in Flutter

You have already prepared the getItem handler in the platforms. Now, you can call it from Flutter.

First, open MultiGallerySelectPage.dart and update the code in the _getItem() placeholder to the following:

// 1
if (_itemCache[index] != null) {
  return _itemCache[index];
} else {
  // 2
  var channelResponse = await _channel.invokeMethod("getItem", index);
  // 3
  var item = Map<String, dynamic>.from(channelResponse);

  // 4
  var galleryImage = GalleryImage(
      bytes: item['data'],
      id: item['id'],
      dateCreated: item['created'],
      location: item['location']);

  // 5
  _itemCache[index] = galleryImage;

  // 6
  return galleryImage;
}

This achieves the following:

  1. Checks the itemCache for entries on the same index. Return it from cache if it exists.
  2. If not, you invoke the getItem method on the channel. You also pass the index as an argument.
  3. You convert the unstructured response into a map. You know the format of the data that you will receive from the previous section.Dynamic is the data type used for because the values are different. They are either strings or int.
  4. Put each value from the Map into a GalleryImage.
  5. Put the image data into the cache.
  6. Finally, return the GalleryImage instance.

At this point, you should be able to see the images from your gallery. Build and run the project on both Android and iOS. You should see your images, and you should be able to select multiple images.

It’s a good thing our image picker can select multiple images — it’s hard to pick just one of those cat memes!

Challenge: Turn Your Project Into a Plugin

Now, you have a working image picker. You might wonder how to share your awesome code with the community. There is a better way to package this code that includes the platform-specific parts: Flutter Plugins.

You might actually be using plugins already. An example is the URL launcher plugin. It allows you to open URLs from Flutter. This plugin has platform code inside. Another example is the battery plugin. This obviously needs platform code to read the battery level.

To convert your project into a plugin, you need to make a Flutter interface that handles the channels. The user of the plugin only needs to know about this interface. How it works inside with channels should be abstracted.

You can read more about plugins here.

You can download the completed project files by clicking on the Download Materials button at the top or bottom of the tutorial.

You may notice that, if you do a fresh install of the final project on Android, you will not be able to see images the first time you run the app. This is issue is outside the scope of this tutorial. However, it might be a nice challenge to tackle next.

If you’re interested, check out the official documentation for platform channels, here.

There is also a good guide on how to make effective plugins here.

We hope you enjoyed this tutorial! If you have any questions or comments, please join the forum discussion below.