Platform Channels: Solution for using a native library in Flutter.

An Lam
FlutterVN
Published in
5 min readOct 23, 2019
Platform Channel Architectural

Flutter now is a trend for the mobile cross-platform framework. Besides of beautiful UI which Flutter brings to us, performance is one of the important points we need to take care of. Of course, performance in Flutter is better when compared with other cross-platform frameworks. With a mobile developer, a background thread is very important to develop the app, and isolate in dart is the solution for us. I have a post about isolate, you can check how to use it.

However, with someone who begins with Flutter, isolate is not easy to use. Actually, isolate is a process, not a thread, so it does not share the resource. Another solution is, continue using a native background thread using Platform Channels.

What is Platform Channels?

Platform Channels is a solution to help us connect to native Android/iOS code. With Platform Channels, as long as native can do, Flutter can also. In the above example, background thread, you can connect to native Android/iOS, use thread as normal, then return data to the Flutter app.

Another example, you are iOS developer, usually use Alamofire to connect to the network. With Flutter, you are also want to continue to use Alamofire, it completely possible.

You can find a lot of popular libraries use Platform Channels, for example, photo_manager, firebase_storage, camera, …

When should we use it?

The answer depends on who you are, what technology you use in your application.

If you have been a native mobile developer, you are familiar with native code, native world is also your world. However, you didn’t have any experience with the native platform before, it’s not easy for you to use platform channels.

In some technics, Dart in Flutter can handle for you, but in some cases, it can’t. For example, accessing Camera, Photo, Bluetooth, Webview, device info,… It depends on each platform Android or iOS.

In addition, in some case, although Dart can support, like isolate or thread, because it’s hard to use, someones don’t like it, they decide to use thread in native.

In my case, I want to get md5 hash of the file, although Dart can support getting md5, it’s lower than when I use platform channels. Thus I decide to use platform channels to improve the app’s performance. You can check my plugin on the pub.

Practice with my demo

You can create Plugin manually but for convenience, you should create a Flutter Plugin support by Editor, it will generate some pieces of code. All you need are rename to match your case, then write the code in native Android and iOS. In my case, I use Android Studio. Below picture shows how to create a plugin from Android Studio

Creating a Flutter Plugin

After following some steps in the wizard, you’ve already created a simple plugin for yourself. I named my plugin is md5_plugin. And you can see your plugin as below:

File md5_plugin.dart in lib folder will be Dart class use Method Channel to call to native Android and iOS. Your native Android/iOS code will be put in the android/ios folder.

Replace the code in md5_plugin.dart as below:

///Plugin class for getting MD5 String from Platform Channel.
class Md5Plugin {
static const MethodChannel _channel = MethodChannel('md5_plugin');

///Method to get MD5 String
///- [filePath] : path of local file
static Future<String> getMD5WithPath(String filePath) async {
var map = {
'file_path': filePath,
};
var checksum = await _channel.invokeMethod<String>('getMD5', map);
return checksum;
}
}

Then let handle getMD5 method for iOS and Android.

Open Md5Plugin.m, then change 2 methods in Md5Plugin class as below:

+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
FlutterMethodChannel* channel = [FlutterMethodChannel
methodChannelWithName:@"md5_plugin"
binaryMessenger:[registrar messenger]];
Md5Plugin* instance = [[Md5Plugin alloc] init];
[registrar addMethodCallDelegate:instance channel:channel];
}

- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
if ([@"getMD5" isEqualToString:call.method]) {
NSString *path = call.arguments[@"file_path"];
result([self fileMD5WithPath: path]);
} else {
result(FlutterMethodNotImplemented);
}
}

What you actually do for this plugin is write code for the `fileMD5WithPath` method. Let do it:

-(NSString *)fileMD5WithPath:(NSString*)path {

NSFileHandle *handle = [NSFileHandle fileHandleForReadingAtPath:path];
if (!handle) return nil;
unsigned int length = 32 * 1024;
NSMutableData *result = [NSMutableData dataWithLength:CC_MD5_DIGEST_LENGTH];
CC_MD5_CTX context;
CC_MD5_Init(&context);
BOOL done = NO;
while (!done) {
@autoreleasepool {
NSData* fileData = [handle readDataOfLength:length];
CC_MD5_Update(&context, [fileData bytes], (unsigned int)[fileData length]);
if( [fileData length] == 0 ) done = YES;
}
}
CC_MD5_Final([result mutableBytes], &context);
[handle closeFile];
NSData *data=[NSData dataWithData:result];
return [data stringWithBase64EncodedData];
}

Also with Android, we will do the same, you can check source code in Md5Plugin.java.

Now you’ve already completed your plugin. Easy?

The final task now is testing your plugin. You can test in 2 ways: unit test or run an actual app.

Unit test

Open md5_plugin_test.dart in folder test, replace with the following code:

void main() {
var channel = MethodChannel('md5_plugin');

setUp(() {
channel.setMockMethodCallHandler((MethodCall methodCall) async {
return 'M78Wt4LB0sUBb+LcR8HfRg==';
});
});

tearDown(() {
channel.setMockMethodCallHandler(null);
});

test('getMD5WithPathOnAndroid', () async {
expect(
await Md5Plugin.getMD5WithPath(
'/data/user/0/com.fluttervn.md5_plugin_example/app_flutter/image.jpg'),
'M78Wt4LB0sUBb+LcR8HfRg==');
});
test('getMD5WithPathOniOS', () async {
expect(
await Md5Plugin.getMD5WithPath(
'/var/mobile/Containers/Data/Application/7720FD4E-A452-4836-BFDA-64E3A735FD30/Documents/image.jpg'),
'M78Wt4LB0sUBb+LcR8HfRg==');
});
}

Then run to check the result. Luckily, it’s passed, the icon on the left of each test function is green:

Unit test result

Run actually app

Go to example folder, open `main.dart` and write code like normal project. With my situation, I write 2 methods, one using my plugin, one using crypto to compare which is faster?

Future<String> calculateMD5SumAsyncWithPlugin(String filePath) async {
var ret = '';
var file = File(filePath);
if (await file.exists()) {
try {
ret = await Md5Plugin.getMD5WithPath(filePath);
} catch (exception) {
print('Unable to evaluate the MD5 sum :$exception');
return null;
}
} else {
print('`$filePath` does not exits so unable to evaluate its MD5 sum.');
return null;
}
return ret;
}

Future<String> calculateMD5SumAsyncWithCrypto(String filePath) async {
var ret = '';
var file = File(filePath);
if (await file.exists()) {
try {
var md5 = crypto.md5;
var hash = await md5.bind(file.openRead()).first;
ret = base64.encode(hash.bytes);
} catch (exception) {
print('Unable to evaluate the MD5 sum :$exception');
return null;
}
} else {
print('`$filePath` does not exits so unable to evaluate its MD5 sum.');
return null;
}
return ret;
}

And the result:

Demo result

Demo source code

Conclusion

Platform Channels is easy. Right? Hope that my article helps you have a good solution for your decision. If it was helpful for you, click on the claps and be sure to follow me on Medium, Facebook, Github.

--

--