A Flutter Channel to Share a File on iOS and Android

Andreas Katzian
The Jade Mind
Published in
6 min readNov 28, 2019

--

Flutter is a great new approach for cross-platform development without a doubt. In many cases, you will get away with standard features. Still, at some point, you need some platform-specific functionality. That’s when Platform Channels come in handy.

In our showcase today, we want to share a file by using the platform-specific sharing dialog. Using channels, we will create separate implementations for Android and iOS and also take some UI-specific aspects into account.

Channels

To write custom platform-specific code, Flutter uses message channels to communicate between your Flutter app (the client) and the specific platform part of your application (the host). So rather than build upon additional, generated code, you have to implement your custom behavior for Android or iOS separately within the language of your choice (Java, Kotlin, Swift, Objective-C). After that, you can use Flutter’s platform-specific API to activate the desired functionality and receive results through the message channel, respectively.

Flutter provides a compact, architectural overview of the Channels concept to show the clear separation of implementation points and the message communication channel to use for exchanging messages.

Flutter’s architectural overview of platform channels
Flutter’s Platform Channel Architecture (Source: flutter.dev)

When writing your custom code, you can use all platform-specific APIs to use frameworks, device sensors or other hardware capabilities. There are no limitations since you are implementing your features within the native environment. Furthermore, all messages and responses are exchanged asynchronously between the host and the client. This procedure will ensure UI responsiveness.

Message Data Types

To exchange data effectively, Flutter uses a so-called message codec. Data will be serialized into a binary format by using a simple key-value representation. The serialization and deserialization of all messages and responses happen automatically.

When receiving a message within your platform-specific implementation, the deserialization of data will also respect the given data types. Furthermore, it will cast the data to platform-specific data types.

The following table illustrates some data type conversions:

+--------+----------------------------+---------------------+
| Dart | Objective-C | Java |
+--------+----------------------------+---------------------+
| bool | [NSNumber numberWithBool:] | java.lang.Boolean |
| int | [NSNumber numberWithInt:] | java.lang.Integer |
| String | NSString | java.lang.String |
| List | NSArray | java.util.ArrayList |
+--------+----------------------------+---------------------+

To get the full overview of all supported data types, you can consult the StandardMessageCodec class documentation. Keep in mind that lists and maps are restricted to the supported data types too. However, you can implement a custom message codec on your own if you need to exchange complex data.

Implementation Details

Once you got the basics about platform channels and if you are already familiar with Kotlin and Swift, you can head over to your custom implementation. To initiate a platform-specific sharing dialog on Android and iOS and to provide a file to share, you need to follow three simple steps:

  1. Open a method channel in your Dart code and call a method passing in your file data.
  2. Implement the channel callback method within iOS and present a share dialog using UIActivityViewController.
  3. Implement the channel callback method within Android and start a share intent.

Dart Code

To simplify and reuse the sharing code, we are going to introduce a Share class providing the capability to share a file using a method channel. Utilizing a channel is straight forward. Create a channel by using the constructor of MethodChannel and give it a unique channel identifier. This identifier will be used within your platform-specific implementation to register a channel callback and to receive messages.

To send a message, simply create a Map of data you want to submit and invoke a remote method by calling the method invokeMethod. Again you use another identifier to specify the method to call. Below you find the full implementation.

Implementation to share a file through Flutter’s method channel

As you may have noticed, the file method has an optional Rect rect parameter. This parameter can be used to provide the location of a Widget (e.g.RaisedButton) the share action was initiated on. This comes in handy when using the sharing dialog on an iPad. Since iOS will display a modal popover, it needs the origin of the trigger to show a corresponding arrow indicator.

Modal popover on iPad with an arrow indicator

Finally, initiate the sharing of a file by using the code in your Dart implementation. You can also provide a subject to display it at the sharing dialog or use it as an e-mail subject.

await Share.file("Sharing an example file", 'Example.pdf', filepath, 'application/pdf', rect: widgetRect);

iOS Code

In your iOS AppDelegate you need to open the flutter method channel with the same channel identifier you are using in your Dart code. After that, set a method call handler and check the id of the method attribute. By accessing the arguments attribute, you will receive all data passed through the method channel.

iOS AppDelegate to open method channel and listen to the method call

Next, add the specific sharing code within a separate method. Here, each value will be extracted from the arguments parameter and cast to their specific data types. Create an instance of UIActivityViewController, set all activity items, including the path of your file you want to share, and the source rectangle the share action was initiated from.

iOS implementation to present a file-sharing dialog

When showing the activity view controller, you will see the default iOS share dialog with the given subject and your attached file. At this point, you finished your first platform channel and successfully used a platform-specific API on your own.

Android Code

To complete the whole story, we do have to implement the custom platform channel code within your Android app too. Open your main activity file and fill in the code, as shown below.

Android MainActivity class handling a platform channel to share a file

It is no surprise at all that the Android code looks quite similar to the iOS code. You have to open the platform channel, get the call arguments and present the sharing activity. In this case, we do not have to specify any location parameters due to the fact that the Android sharing dialog does not have a corresponding trigger indicator.

Now you are ready to build your Flutter app and test your platform-specific file-sharing feature on your simulators or real devices.

Dart packages

If you want to have some production-ready sharing capabilities, you can use some available Dart packages to get a head start for your application. Depending on the selected package, you may face some limitations, but using the presented guide to implement it on your own should be a no-brainer.

Listed below, you will find selected Dart packages for sharing content and/or files, which are available and maintained:

Furthermore, you will find many more implementations when searching for sharing packages.

Conclusion

In this article, you got a comprehensive overview of Flutter`s platform channels and how to extend your Flutter app with platform-specific features. Check out Flutter`s documentation to gain further insights and play around with different code examples.

If you found a solution for a common problem with your platform-specific implementation, feel free to share it with the community. To do so, you should deal with advanced topics like developing and publishing Dart packages.

Happy channeling (:

--

--

Andreas Katzian
The Jade Mind

Passionate software developer and problem solver in topics like platform development, mobile, data integration and digital transformation.