Flutter Plugins Development: Extending with Native Code
29 June
Flutter’s strength lies in its ability to create beautiful and performant cross-platform apps using a single codebase. However, there are times when you need to access platform-specific features or leverage existing native libraries. Flutter plugins provide a way to bridge the gap between Flutter and native code, allowing developers to extend Flutter’s capabilities by integrating native code into their apps. In this article, we will explore the development of Flutter plugins and demonstrate how to integrate native code into your Flutter project with code examples.
Creating a Flutter Plugin
To create a Flutter plugin, you need to follow a specific structure and utilize platform-specific code. Let’s consider an example of creating a simple Flutter plugin for displaying a native toast message:
import 'dart:async'; import 'package:flutter/services.dart'; class FlutterToast { static const MethodChannel _channel = MethodChannel('flutter_toast'); static Future<void> showToast(String message) async { try { await _channel.invokeMethod('showToast', {'message': message}); } catch (e) { print('Failed to show toast: $e'); } } }
In this code example, we define a FlutterToast class with a static method showToast that accepts a message parameter. The MethodChannel class from the flutter/services package is used to communicate with the platform-specific code. We invoke the native method showToast through the _channel object, passing the message as a parameter.
Implementing Native Code
To integrate native code into your Flutter plugin, you need to implement the platform-specific functionality for each supported platform. Let’s see how the native implementation would look like for both Android and iOS platforms:
Android:
package com.example.fluttertoast; import android.content.Context; import android.widget.Toast; import androidx.annotation.NonNull; import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding.OnSaveInstanceStateListener; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding.StartActivityForResultListener; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel.MethodCallHandler; import io.flutter.plugin.common.MethodChannel.Result; import io.flutter.plugin.common.PluginRegistry.Registrar; public class FlutterToastPlugin implements FlutterPlugin, MethodCallHandler { private Context context; private MethodChannel channel; public static void registerWith(Registrar registrar) { final MethodChannel channel = new MethodChannel(registrar.messenger(), "flutter_toast"); channel.setMethodCallHandler(new FlutterToastPlugin()); } @Override public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { context = binding.getApplicationContext(); channel = new MethodChannel(binding.getBinaryMessenger(), "flutter_toast"); channel.setMethodCallHandler(this); } @Override public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { if (call.method.equals("showToast")) { String message = call.argument("message"); showToast(message); result.success(null); } else { result.notImplemented(); } } private void showToast(String message) { Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); } @Override public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { context = null; channel.setMethodCallHandler(null); } }
iOS (Objective-C/Swift):
#import "FlutterToastPlugin.h" #import <flutter_toast/flutter_toast-Swift.h> @implementation FlutterToastPlugin + (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar { FlutterMethodChannel* channel = [FlutterMethodChannel methodChannelWithName:@"flutter_toast" binaryMessenger:[registrar messenger]]; FlutterToastPlugin* instance = [[FlutterToastPlugin alloc] init]; [registrar addMethodCallDelegate:instance channel:channel]; } - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { if ([call.method isEqualToString:@"showToast"]) { NSString* message = call.arguments[@"message"]; [self showToast:message]; result(nil); } else { result(FlutterMethodNotImplemented); } } - (void)showToast:(NSString*)message { [[FlutterToastPluginSwift shared] showToast:message]; } @end
Conclusion
Flutter plugins allow developers to leverage the power of native code in their Flutter apps, enabling access to platform-specific features and existing libraries. In this article, we explored the process of creating a Flutter plugin, including writing Flutter and native code. We learned how to create a plugin class, invoke native methods, and implement the platform-specific functionality for Android and iOS. By mastering the art of Flutter plugins, you can extend Flutter’s capabilities and create even more powerful and versatile applications. Happy coding!