Edo Lubis
Posted on April 1, 2024
When building applications with Flutter, Developers often rely on packages/plugins from pub.dev for their projects, whether developed by Google's team or by others. Utilizing these packages/plugins is highly beneficial as it can faster the development process, saving developers time by avoiding the need to create everything from scratch.
but consider this scenario…
Imagine you have developed a fantastic widget that enhances the functionality of your application. Wouldn't it be better to use that code in other of your projects without rewriting it?
or Imagine, where the package/plugin you heavily rely on becomes discontinued or unsupported. You're left in a dilemma, uncertain about how to proceed with your project.
In these scenario, the solution is create your own Flutter packages.
Creating Packages
Let's illustrate this with an example. Suppose we want to create a package to show a beautiful modal and we call the package name "beautiful_modal".
1. Create a new Flutter project using Android Studio and select the project type as a package.
2. After the project is successfully created, the folder structure will look like this.
3. Code your package according to your needs. For example, for the "beautiful_modal" package, the code will look like this:
library beautiful_modal;
import 'package:flutter/material.dart';
void showBeautifulDialog(BuildContext context, String title, String subtitle) {
showDialog(
context: context,
builder: (BuildContext context) {
return BeautifulModal(
subTitle: subtitle,
title: title,
);
},
);
}
class BeautifulModal extends StatelessWidget {
final String title;
final String subTitle;
const BeautifulModal({Key? key, required this.title, required this.subTitle})
: super(key: key);
@override
Widget build(BuildContext context) {
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.0),
),
elevation: 0,
backgroundColor: Colors.transparent,
child: contentBox(context),
);
}
Widget contentBox(BuildContext context) {
return Stack(
children: <Widget>[
Container(
padding:
const EdgeInsets.only(left: 20, top: 50, right: 20, bottom: 20),
margin: const EdgeInsets.only(top: 20),
decoration: BoxDecoration(
shape: BoxShape.rectangle,
color: Colors.white,
borderRadius: BorderRadius.circular(16),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const SizedBox(
height: 100,
width: 100,
child: FlutterLogo(),
),
const SizedBox(height: 8),
Text(
title,
style:
const TextStyle(fontSize: 22, fontWeight: FontWeight.w600),
),
const SizedBox(height: 8),
Text(
subTitle,
style: const TextStyle(fontSize: 14),
),
const SizedBox(height: 8),
Align(
alignment: Alignment.bottomRight,
child: ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text(
'Close',
style: TextStyle(fontSize: 18),
),
),
),
],
),
),
],
);
}
}
Creating Plugins
You might wonder what a plugin is and how it differs from a package. Simply put, a plugin is a type of package designed to act as a bridge between Flutter's Dart code and platform-specific APIs on native operating systems like iOS and Android. So, all plugins are packages, but not all packages are plugins.
For example, let's say we want to show a native modal from iOS or Android.
The modal above is not created using Flutter code; instead, it's the default modal provided by the operating system (OS).
1. Create a new Flutter project using Android Studio and select the project type as a plugin.
2. After the project is successfully created, the folder structure will look like this.
Dart Code
1. Open the beautiful_native_modal_plaform_interface.dart
file and replace the getPlatformVersion
function with showNativeModal
.
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
import 'beautiful_native_modal_method_channel.dart';
abstract class BeautifulNativeModalPlatform extends PlatformInterface {
/// Constructs a BeautifulNativeModalPlatform.
BeautifulNativeModalPlatform() : super(token: _token);
static final Object _token = Object();
static BeautifulNativeModalPlatform _instance = MethodChannelBeautifulNativeModal();
/// The default instance of [BeautifulNativeModalPlatform] to use.
///
/// Defaults to [MethodChannelBeautifulNativeModal].
static BeautifulNativeModalPlatform get instance => _instance;
/// Platform-specific implementations should set this with their own
/// platform-specific class that extends [BeautifulNativeModalPlatform] when
/// they register themselves.
static set instance(BeautifulNativeModalPlatform instance) {
PlatformInterface.verifyToken(instance, _token);
_instance = instance;
}
Future<void> showNativeModal(String message) {
throw UnimplementedError('platformVersion() has not been implemented.');
}
}
2. Open the beautiful_native_modal_method_channel.dart file and implement the showNativeModal function.
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'beautiful_native_modal_platform_interface.dart';
/// An implementation of [BeautifulNativeModalPlatform] that uses method channels.
class MethodChannelBeautifulNativeModal extends BeautifulNativeModalPlatform {
/// The method channel used to interact with the native platform.
@visibleForTesting
final methodChannel = const MethodChannel('beautiful_native_modal');
@override
Future<void> showNativeModal(String message) async {
await methodChannel.invokeMethod<String>('showNativeModal', {"message": message});
}
}
The string showNativeModal
in the methodChannel.invokeMethod<String>
function represents the name of the method to be executed on the Android or iOS side. Additionally, {"message": message}
serves as the parameter being sent along with this method call.
3. Add the showNativeModal function to the beautiful_native_modal.dart file.
import 'beautiful_native_modal_platform_interface.dart';
class BeautifulNativeModal {
Future<void> showNativeModal(String message) {
return BeautifulNativeModalPlatform.instance.showNativeModal(message);
}
}
Plugins in Android
1. Open the BeautifulNativeModalPlugin.kt
file in the android
folder and add the function to call the dialog from Android.
private fun showNativeModal(message: String?) {
val builder: AlertDialog.Builder = AlertDialog.Builder(activity)
builder.setTitle("Modal")
builder.setMessage(message)
builder.setPositiveButton("OK") { dialog, _ -> dialog.dismiss() }
val dialog: AlertDialog = builder.create()
dialog.show()
}
2. Call the showNativeModal function from the onMethodCall method.
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
if (call.method == "showNativeModal") {
val message = call.argument<String?>("message")
showNativeModal(message)
result.success(null)
} else {
result.notImplemented()
}
}
3. Then, the full code will look like this:
package com.example.beautiful_native_modal
import android.app.Activity
import android.app.AlertDialog
import android.content.DialogInterface
import androidx.annotation.NonNull
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.embedding.engine.plugins.FlutterPlugin
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
/** BeautifulNativeModalPlugin */
class BeautifulNativeModalPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
/// The MethodChannel that will the communication between Flutter and native Android
///
/// This local reference serves to register the plugin with the Flutter Engine and unregister it
/// when the Flutter Engine is detached from the Activity
private lateinit var channel: MethodChannel
private var activity: Activity? = null
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "beautiful_native_modal")
channel.setMethodCallHandler(this)
}
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
activity = binding.activity
}
override fun onDetachedFromActivityForConfigChanges() {
activity = null
}
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
activity = binding.activity
}
override fun onDetachedFromActivity() {
activity = null
}
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
if (call.method == "showNativeModal") {
val message = call.argument<String?>("message")
showNativeModal(message)
result.success(null)
} else {
result.notImplemented()
}
}
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
}
private fun showNativeModal(message: String?) {
val builder: AlertDialog.Builder = AlertDialog.Builder(activity)
builder.setTitle("Modal")
builder.setMessage(message)
builder.setPositiveButton("OK") { dialog, _ -> dialog.dismiss() }
val dialog: AlertDialog = builder.create()
dialog.show()
}
}
Plugins in iOS
1. Open the BeautifulNativeModalPlugin.swift
file in the ios
folder and add the function to display the modal in native iOS.
private func showNativeModal(message: String) {
let alert = UIAlertController(title: nil, message: message, preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "OK", style: .default) { _ in }
alert.addAction(defaultAction)
guard let controller = controller else {
return
}
controller.present(alert, animated: true)
}
2. Call the showNativeModal function from the handle method.
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "showNativeModal":
if let args = call.arguments as? [String: Any],
let message = args["message"] as? String {
showNativeModal(message: message)
result(nil)
} else {
result(FlutterError(code: "INVALID_ARGUMENT",
message: "Missing or invalid argument",
details: nil))
}
default:
result(FlutterMethodNotImplemented)
}
}
3. Then the full code will look like this
import Flutter
import UIKit
public class SwiftBeautifulNativeModalPlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "beautiful_native_modal", binaryMessenger: registrar.messenger())
let instance = SwiftBeautifulNativeModalPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "showNativeModal":
if let args = call.arguments as? [String: Any],
let message = args["message"] as? String {
showNativeModal(message: message)
result(nil)
} else {
result(FlutterError(code: "INVALID_ARGUMENT",
message: "Missing or invalid argument",
details: nil))
}
default:
result(FlutterMethodNotImplemented)
}
}
private func showNativeModal(message: String) {
let alert = UIAlertController(title: nil, message: message, preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "OK", style: .default) { _ in }
alert.addAction(defaultAction)
guard let controller = controller else {
return
}
controller.present(alert, animated: true)
}
private var controller: UIViewController? {
return UIApplication.shared.keyWindow?.rootViewController
}
}
Publish package/plugin to pub.dev
If you want to share your packages/plugins with the Flutter community, publishing it on pub.dev allows other developers to easily discover and use your package.
1. Create a pub.dev Account
To publish packages on pub.dev, you need to create an account. Visit the pub.dev website and sign in with your Google account.
2. Prepare Your Pubspec
Ensure your pubspec.yaml file contains accurate metadata for your package, including the name, version, description, author, homepage, and dependencies. You can also include tags to help users discover your package.
3. Publishing Your Package
Once your package is ready, Run the command flutter pub publish --dry-run
then run flutter pub publish
.
For more details on publishing, see the publishing docs on dart.dev.
Conclusion
In conclusion, Flutter packages and plugins are powerful tools. Developers can save time and effort by reusing code across projects. This eliminates the need to rewrite the same functionality from scratch. By utilizing packages from pub.dev or creating custom packages and plugins, developers can enhance productivity, reuse code, and access native features seamlessly across different platforms.
that's it, thankyou
Reference:
Developing packages & plugins
Writing custom platform-specific code
Posted on April 1, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.