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.
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
Platform-Specific Code With Flutter Method Channel: Getting Started
30 mins
- Getting Started
- Understanding the Method Channel
- Setting up Method Channel on iOS With Swift
- Understanding PhotoKit
- Requesting Permissions on iOS
- Fetching all Photos From iOS
- Reading Image Data From iOS
- Setting up the Method Channel on Flutter
- Building a Custom Image Provider in Flutter
- Rendering Images From the Host Device
- Rendering Selected Images
- Setting up Method Channel on Android with Kotlin
- Understanding Android’s Media API
- Requesting User Permissions on Android
- Fetching all Images From Android
- Reading Image Bytes on Android
- Consuming Method Calls From Host Platforms in Flutter
- Where to Go From Here?
Fetching all Images From Android
Still in MainActivity
, declare another function that will execute the query and return the resultant cursor:
private fun getCursor(limit: Int): Cursor? {
//1
val uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
val projection = arrayOf(MediaStore.Images.Media._ID)
//2
return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
val sort = "${MediaStore.Images.ImageColumns.DATE_MODIFIED} DESC LIMIT $limit"
contentResolver.query(uri, projection, null, null, sort)
} else {
//3
val args = Bundle().apply {
putInt(ContentResolver.QUERY_ARG_LIMIT, limit)
putStringArray(
ContentResolver.QUERY_ARG_SORT_COLUMNS,
arrayOf(MediaStore.Images.ImageColumns.DATE_MODIFIED)
)
putInt(
ContentResolver.QUERY_ARG_SORT_DIRECTION,
ContentResolver.QUERY_SORT_DIRECTION_DESCENDING
)
}
contentResolver.query(uri, projection, args, null)
}
}
Here's what the code above does:
- Declares the
uri
andprojection
to get image id column from external storage. - Executes the query API for devices having SDK versions of Android earlier than Android 11.
- Executes the query API for devices having an SDK version of Android 11 or higher.
Finally, replace the dummy code in getPhotos()
with:
if (queryLimit == 0 || !hasStoragePermission()) return
lifecycleScope.launch(Dispatchers.IO) {
val ids = mutableListOf<String>()
val cursor = getCursor(queryLimit)
cursor?.use {
while (cursor.moveToNext()) {
val columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
val long = cursor.getLong(columnIndex)
ids.add(long.toString())
}
}
methodResult?.success(ids)
}
While Coroutines was used to execute the query on a background thread, the cursor was iterated over to get the id
of each image.
Reading Image Bytes on Android
To read the image bytes for a given id
, you'll first get the Uri
for the image. Then, you'll request the bytes with Glide, an Android image-loading library.
Write the getImageBytes
function above getCursor()
:
private fun getImageBytes(uri: Uri?, width: Int, height: Int, onComplete: (ByteArray) -> Unit) {
lifecycleScope.launch(Dispatchers.IO) {
try {
val r = Glide.with(this@MainActivity)
.`as`(ByteArray::class.java)
.load(uri)
.submit(width, height).get()
onComplete(r)
} catch (t: Throwable) {
onComplete(byteArrayOf())
}
}
}
The instructions above load the image with the uri
and invoke onComplete()
with the resultant bytes.
Finally, replace the dummy code in fetchImage()
:
// 1
val id = (args["id"] as String).toLong()
val width = (args["width"] as Double).toInt()
val height = (args["height"] as Double).toInt()
// 2
val uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id)
getImageBytes(uri, width, height) {
result.success(it)
}
Here's what this code does:
- Reads the image properties from the arguments passed from the Dart end.
- Generates a Uri with the ID and loads the image for that Uri.
Run the Flutter project on Android, and you should have a similar experience:
Consuming Method Calls From Host Platforms in Flutter
Host platforms can invoke a method on Flutter, and Flutter can listen for incoming method invocations and parse the method names and arguments, just like you did for Swift and Kotlin above. This is another way in which the Event Channel differs from the Method Channel, because the events in the Event Channel are unidirectional.
Android can send the method call to Flutter like this:
val param = mapOf(Pair("param1", "value1"), Pair("param2", "value2"))
methodChannel.invokeMethod("doSomething", param)
Swift on iOS can send it like this:
var param = ["param1": "value1", "param2": "value2"]
methodChannel.invokeMethod("doSomething", arguments: param)
In both examples above, invokeMethod()
supports a third argument, which is the result Flutter returns.
methodChannel.setMethodCallHandler((call) async {
switch (call.method) {
case 'doSomething':
return doSomething(call.arguments);
default:
throw PlatformException(code: '1', message: 'Not Implemented');
}
});
Congrats on completing this tutorial!
Where to Go From Here?
The completed project contains the full code used in this tutorial. It's named final in the zipped file you downloaded earlier. You can still download it by clicking Download Materials at the top or bottom of this tutorial.
In this tutorial, you learned how to communicate between Flutter and the host platform via the Method Channel. To improve your knowledge, you can also implement the camera and video features. When the user taps the camera icon, call up the host platform to capture an image and return it to Flutter. The same goes for the video icon, but capture and return video data instead.
Check out the official doc on Writing custom platform-specific code and video on Packages and Plugins to learn how to develop a plugin that uses platform channel to talk to the pressure sensor on Android and iOS devices. And of course, if you have any questions or comments, please join the forum discussion below!