Flutter Packages — Part 2

Amrit Pandey
5 min readMar 31, 2024

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

  1. Package is still in the development stage and not ready to be distributed to other teams or publicly.
  2. 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

  1. Before hosting your package make sure you add the package name, version, dependencies and a brief description to pubspec.yaml file.
  2. Create an account on pub.dev.
  3. Make sure your package name is unique and does not conflict with any existing package on pub.dev.
  4. 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.g 1.0.0
  5. 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.
  6. 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
  7. Whenever you make updates to your package, remember to increment the version number in the pubspec.yaml file and use flutter 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!

--

--