Creating your own packages and plugins in flutter
Tags: flutter, mobile, tutorial
Published: 2024-04-01 03:45:48

When building applications with Flutter, Developers often rely on packages/plugins from [pub.dev](https://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.
![Create a new Flutter project](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sqdwn1tuue7ji5x84f2k.png)

**2.** After the project is successfully created, the folder structure will look like this.
![folder structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1f3bdscbakpmjnjdchua.png)

**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),
                  ),
                ),
              ),
            ],
          ),
        ),
      ],
    );
  }
}
```

**4.** Run and test your package.
![Test your package](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/73xvnxreqyh4qas7yniy.png)

## 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.
![Native Modal](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hxljobkk5b3bvb5j50ld.png)

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.
![Create a new Flutter project](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uifk0fb4nb4q31yyizc3.png)

**2.** After the project is successfully created, the folder structure will look like this.
![folder structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/20grz1buffybbymyqt4m.png)

#### 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()
    }
}
```

**4.** Run and test your code
![Android Dialog](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h2risl4rwwmv09qo6x7t.png)

#### 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
   }
}
```

**4.** Run and test your plugin.
![Ios Dialog](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cu9sbv70j1p6uos5cnhb.png)

## Publish package/plugin to [pub.dev](https://pub.dev)
If you want to share your packages/plugins with the Flutter community, publishing it on [pub.dev](https://pub.dev) allows other developers to easily discover and use your package.

**1. Create a [pub.dev](https://pub.dev) Account**
To publish packages on [pub.dev](https://pub.dev), you need to create an account. Visit the [pub.dev](https://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](https://dart.dev/tools/pub/publishing) 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](https://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](https://docs.flutter.dev/packages-and-plugins/developing-packages)
[Writing custom platform-specific code](https://docs.flutter.dev/platform-integration/platform-channels)