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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Your Own Image Picker With Flutter Channels
25 mins
- Getting Started
- Flutter and Platform-Specific Code
- Platform Channels
- Method Channels
- BasicMessageChannel
- Writing Your Platform-Specific Code
- Getting Images From the Gallery
- Setting Up Platform Channels
- Platform Channels in Flutter
- Platform Channels in Android
- Platform Channels in iOS
- Encoding the Gallery Data for Flutter
- Decoding the Gallery Data in Flutter
- Challenge: Turn Your Project Into a Plugin
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:
- Open a channel with the same name that you previously used (
/gallery
). - Set a handler for the channel.
- 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:
- Get the view controller instance so that you can open a channel.
- Open a channel with the same name that you previously used (
/gallery
). - Set a handler for the channel.
- 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:
- Here, you add a case to
when
. You perform this case when the call method isgetItem
. - Parse the call arguments. You are assuming that the argument is an integer.
- Get the associated data for this index. Call the
dataForGalleryItem
method that you made earlier. - 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:
- Checks the
itemCache
for entries on the same index. Return it from cache if it exists. - If not, you invoke the
getItem
method on the channel. You also pass the index as an argument. - 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 eitherstrings
orint
. - Put each value from the Map into a
GalleryImage
. - Put the image data into the cache.
- 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.