Flutter Packages — Part 2
This is part 2 of the Flutter package series, I am going to talk about the plugin package.
The plugin package can contain one or more platform-specific implementations. At the time of creating a plugin package, supported platform/s can be added to the below command for adding respective platform folders eg iOS, Android, windows, Linux, macOS, web.
flutter create --template=plugin --platforms=ios,android,windows,linux,web,macos hello
For Android and iOS, flutter provides an option to add preferred language like kotlin(default), Java, objc, swift(default)
flutter create --template=plugin --platforms=ios,android,windows,linux,web,macos -a java -i objc hello
Folder structure
Let's talk about the folder structure generated from the above command. A lib
folder along with pubspec.yaml
file exists inside plugin folder along with respective platform folder. There is<plugin_name>.dart
, this is the class exposed to client. To expose more classes to clients add export
statements for different class export ‘lib/class_to_export.dart’;
.
Exposed classes can contain a widget
or a simple functions
or a Future
. Export classes can contain both stateless and stateful widget.
The pubspec
file contains a dependency plugin_platform_interface
package, which is used to establish channel with platform-specific native code. One can also use pigeon package as a replacement of plugin_platform_interface
. Flutter section of this file recognise project as a plugin or a package, see below to configure pubspec file as a plugin for iOS and Android.
flutter:
# This section identifies this project as a plugin project.
# The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.)
# which should be registered in the plugin registry. This is required for using method channels.
# The Android 'package' specifies package in which the registered class is.
# This is required for using method channels on Android.
plugin:
platforms:
ios:
pluginClass: FlutterPluginiOS
android:
package: net.amrit.android_plugin
pluginClass: FlutterPluginAndroid
For more information on configuring pubspec
file, go to this link.
Method channel
Method channels are used to send and recieve messages from native code to Flutter via method channel. This is an asynchronous method call to native code.
// Create method channel
final methodChannel = const MethodChannel('hello');
//send message to platform
final result = await methodChannel.invokeMethod<String>('getSomething');
return result;
//implementation in iOS
@implementation FlutterPluginiOS
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
// create method channel to listen message from flutter
FlutterMethodChannel* channel = [FlutterMethodChannel
methodChannelWithName:@"hello"
binaryMessenger:[registrar messenger]];
//Create plugin instance
FlutterPluginiOS* instance = [[HelloPlFlutterPluginiOSugin alloc] init];
[registrar addMethodCallDelegate:instance channel:channel];
}
- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
if ([@"getSomething" isEqualToString:call.method]) {
// send message to flutter
result([@"iOS"]);
} else {
// send error to flutter
result(FlutterMethodNotImplemented);
}
}
@end
invokeMethod
in flutter is a generic method, it is type-safe
method to mitigate the risk of type mismatch between flutter and native code. Only one async message from the native code can be sent to Flutter using the method channel.
Event channel
Event channels are used to open stream channel to send and recieve multiple async messages from native code to Flutter.
//create event channel
final _eventChannel = const EventChannel('hello');
Stream? _onReceiver; // Stream object to listen messages from native code
Stream? getStreamReceiver({String? sendMessage}) {
//send message to native code
_onReceiver ??= _eventChannel.receiveBroadcastStream([sendMessage]);
return _onReceiver;
}
void getMessages() async {
//initialize stream
getStreamReceiver?.listen((value) async {
// get message succesfully
print(value);
}, onError: (error) {
// handle error thrown from native code
print(error);
});
}
var stream: FlutterEventSink? // stream instance on native code
public static func register(with registrar: FlutterPluginRegistrar) {
let instance = FlutterPluginiOS()
//create stream channel on native code
let eventChannel = FlutterEventChannel(name:"hello", binaryMessenger: registrar.messenger())
eventChannel.setStreamHandler(instance)
}
@MainActor public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
stream = events
var flutterMessage: String? = nil
let arguments = arguments as? Array<Any>
if let arg = arguments, arg.count > 0 {
flutterMessage = arg[0] as? String //message from flutter to native code while opening stream connection
}
self?.stream?("native code message") //send message to flutter code
//send error to flutter
//self?.stream?(FlutterError(code: error.code, message: error.value, details: nil))
}
receiveBroadcastStream
sets up a broadcast stream to listen message from native code. Stream
object is a generic object, so it can be type safe but I have kept it dynamic
to send error as FlutterError
and message as String
. FlutterError
can be used to send detail error objects from native platform to flutter.
Note: Depending on use case it is possible to use both Event channel and method channel together.
Host package in same repo
Open pubspec.yaml
file of package add version, environment (SDK and flutter version), any dependencies, name of package, description of package. Hosting packages in the same repo is always a good choice if
- Package is still in the development stage and not ready to be distributed to other teams or publicly.
- If the package is being used by one team.
This can lower the development cost of the package until a stable version is released. Just drop your package outside of the lib
folder and add your package to project pubspec.yaml
file. If the name of package is flutter_plugin_ios
then run flutter pub get
.
flutter_plugin_ios:
path: ./flutter_plugin_ios
Host package to https://pub.dev/
Do follow below steps
- Before hosting your package make sure you add the package name, version, dependencies and a brief description to
pubspec.yaml
file. - Create an account on pub.dev.
- Make sure your package name is unique and does not conflict with any existing package on pub.dev.
- Make sure a valid version number is added to
pubspec.yaml
. For subsequent release increment your version number. pub.dev requires version numbers to be incremented for each new release. e.g1.0.0
- In your terminal or command prompt, navigate to the root directory of your Flutter package and run the
flutter pub publish
command. This command will prompt you to log in with your pub.dev account credentials. Once logged in, it will publish your package to pub.dev. - After publishing, your package will undergo automated verification checks by pub.dev. These checks ensure that your package meets certain quality standards and does not contain any malicious code
- Whenever you make updates to your package, remember to increment the version number in the
pubspec.yaml
file and useflutter pub publish
command.
PS: Always respond to any issues or feedback from users. Regular maintenance helps ensure the quality and reliability of your package.
Thanks for reading Happy coding!