# Understanding the Plugin template in Flutter

## What is a Package?

A package is a set of programs files that provides us with a specific functionality. Say like a International Time Converter, Useful Extensions Provider or may be a custom UI with some complex animations.

There are two types of packages in Flutter:

**Dart packages:**

* These are written in pure dart and have no platform dependency so they basically can be used anywhere Flutter runs.
    
* In fact there are even Dart packages with no dependency on Flutter Framework, these can run in any environment: web, desktop , server and so on.
    
* Eg: intl, custom\_search\_bar, http
    

**Plugin Packages:**

* These are a combination of Dart and Platform specific code.
    
* These packages commonly known as plugins, contain platform-native implementation code.
    
* They are generally used to access platform features such as native camera, native file system or a platform specific feature like Health Data from iOS or Google pedometer data from Google Fit.
    
* Eg: image\_picker, camera\_x
    

From here on out we'll call them by their common names:

* Dart/Flutter Package is Package
    
* Plugin Package is Plugin
    

### Create a Package:

When we want to create a package in Flutter, we will use the Flutter `create` command and set the `template` as package.

So here's an example code

```bash
flutter create --template=package sample_package
```

#### File Structure:

Let's take a look at the file structure of the package:

```bash
.
├── CHANGELOG.md
├── LICENSE
├── README.md
├── analysis_options.yaml
├── lib
│   └── sample_package.dart
├── pubspec.lock
├── pubspec.yaml
├── sample_package.iml
└── test
    └── sample_package_test.dart

3 directories, 9 files
```

<mark>As we see there are no android or iOS folders</mark>. Because as I said a package is a pure dart implementation with no native code necessary.

Note: If you want to create a dart package with no flutter dependency use: dart create --template=package package\_name

## Creating a Plugin

Now if we want to create a Plugin with platforms as android and iOS. We'll use the following command:

```bash
flutter create --template=plugin --platforms=android,ios sample_plugin
```

#### File Structure of a Plugin:

Not only does it come with the usual `lib`, `android`, and `ios` folders:

![](https://i.postimg.cc/pdScVZr4/Screenshot-2024-07-25-at-3-57-42-PM.png align="left")

But it also comes with an example folder, where we'll write the example code on how to integrate the plugin.

Also we generally interact with native code using Method Channels. How to use Method Channels: [Using Platform Channels Article](https://harishkunchala.com/how-to-use-platform-channels-in-flutter)

#### `Pubspec.yaml` of a Plugin.

Now let's take a look at the `pubspec.yaml` of the plugin.

```yaml
name: sample_plugin
description: "A new Flutter plugin project."
version: 0.0.1
homepage:

environment:
  sdk: '>=3.4.4 <4.0.0'
  flutter: '>=3.3.0'

dependencies:
  flutter:
    sdk: flutter
  plugin_platform_interface: ^2.0.2

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^3.0.0

flutter:
  plugin:
    platforms:
      android:
        package: com.example.sample_plugin
        pluginClass: SamplePlugin
      ios:
        pluginClass: SamplePlugin
```

So as we can see the plugin classes are defined on both Android and iOS and we are going to use them for contacting via method channels.

# What is a Method Channel?

For a detailed explanation see: [How to use Platform Channels in Flutter](https://harishkunchala.com/how-to-use-platform-channels-in-flutter)

Method channels allows us to communicate between Flutter and Native Platforms through method invocation on the platform side. We use:

* `MethodChannel` on Android
    
* `FlutterMethodChannel` on iOS for receiving method calls and sending a result back.
    

So basically the platform (Host) listens on the platform channel, and receives a message. It can use it's platform APIs to make the implementation of the logic and send back a response to the Flutter App (Client).

## Implementing the Android Plugin:

We get a default code from Android with an example on MethodChannel to get the build version. Let's see the code:

```kotlin
package com.example.sample_plugin

import androidx.annotation.NonNull

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

/** SamplePlugin */
class SamplePlugin: FlutterPlugin, MethodCallHandler {
  /// 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

  override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
    channel = MethodChannel(flutterPluginBinding.binaryMessenger, "sample_plugin")
    channel.setMethodCallHandler(this)
  }

  override fun onMethodCall(call: MethodCall, result: Result) {
    if (call.method == "getPlatformVersion") {
      result.success("Android ${android.os.Build.VERSION.RELEASE}")
    } else {
      result.notImplemented()
    }
  }

  override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
    channel.setMethodCallHandler(null)
  }
}
```

## Code Explanation:

### Package Declaration and Imports

```kotlin
package com.example.sample_plugin

import androidx.annotation.NonNull

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

* **Package Declaration**: The code is part of the `com.example.sample_plugin` package.
    
* **Imports**: The code imports necessary classes from the Android and Flutter libraries to create a Flutter plugin.
    

### Class Declaration

```kotlin
/** SamplePlugin */
class SamplePlugin: FlutterPlugin, MethodCallHandler {
```

* **Class Declaration**: The `SamplePlugin` class implements two interfaces: `FlutterPlugin` and `MethodCallHandler`.
    

### Properties

```kotlin
private lateinit var channel : MethodChannel
```

* `channel`: This is a `MethodChannel` object that will handle communication between Flutter and the native Android code. It is declared as a `lateinit var`, meaning it will be initialized later.
    

### onAttachedToEngine Method

```kotlin
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
  channel = MethodChannel(flutterPluginBinding.binaryMessenger, "sample_plugin")
  channel.setMethodCallHandler(this)
}
```

* `onAttachedToEngine`: This method is called when the plugin is attached to the Flutter engine.
    
    * `flutterPluginBinding.binaryMessenger`: This is used to create a `MethodChannel` with the name `"sample_plugin"`.
        
    * `channel.setMethodCallHandler(this)`: Sets the current instance (`this`) as the handler for method calls coming through the channel.
        

### onMethodCall Method

```kotlin
override fun onMethodCall(call: MethodCall, result: Result) {
  if (call.method == "getPlatformVersion") {
    result.success("Android ${android.os.Build.VERSION.RELEASE}")
  } else {
    result.notImplemented()
  }
}
```

* `onMethodCall`: This method handles incoming method calls from Flutter.
    
    * `call.method`: Checks the method name of the incoming call.
        
    * `getPlatformVersion`: If the method name is `"getPlatformVersion"`, it returns the Android version of the device.
        
    * `result.success`: Sends the result back to Flutter.
        
    * `result.notImplemented`: If the method is not recognized, it returns a "not implemented" response.
        

### onDetachedFromEngine Method

```kotlin
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
  channel.setMethodCallHandler(null)
}
```

* `onDetachedFromEngine`: This method is called when the plugin is detached from the Flutter engine.
    
    * `channel.setMethodCallHandler(null)`: Unregisters the method call handler by setting it to `null`.
        

### Summary

* The `SamplePlugin` class is a Flutter plugin for Android.
    
* It sets up a `MethodChannel` for communication between Flutter and native Android code.
    
* It handles a specific method call (`getPlatformVersion`) to return the Android version.
    
* It properly manages the lifecycle of the plugin by attaching and detaching from the Flutter engine.
    

This code essentially enables Flutter to call native Android functions and get responses back, which is useful for our platform-specific functionality.

## Implementing the iOS Plugin:

Now let's take a look at the default code provided by iOS. So here's the default code provided in `SamplePlugin.swift`

```swift
import Flutter
import UIKit

public class SamplePlugin: NSObject, FlutterPlugin {
  public static func register(with registrar: FlutterPluginRegistrar) {
    let channel = FlutterMethodChannel(name: "sample_plugin", binaryMessenger: registrar.messenger())
    let instance = SamplePlugin()
    registrar.addMethodCallDelegate(instance, channel: channel)
  }

  public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
    switch call.method {
    case "getPlatformVersion":
      result("iOS " + UIDevice.current.systemVersion)
    default:
      result(FlutterMethodNotImplemented)
    }
  }
}
```

## Code Explanation:

### Imports

```swift
import Flutter
import UIKit
```

* `import Flutter`: Imports the Flutter framework for iOS.
    
* `import UIKit`: Imports the UIKit framework, which provides the necessary infrastructure for constructing and managing iOS apps.
    

### Class Declaration

```swift
public class SamplePlugin: NSObject, FlutterPlugin {
```

* **Class Declaration**: The `SamplePlugin` class inherits from `NSObject` and conforms to the `FlutterPlugin` protocol. This means it can act as a Flutter plugin.
    

### register Method

```swift
public static func register(with registrar: FlutterPluginRegistrar) {
  let channel = FlutterMethodChannel(name: "sample_plugin", binaryMessenger: registrar.messenger())
  let instance = SamplePlugin()
  registrar.addMethodCallDelegate(instance, channel: channel)
}
```

* `register` Method: This static method is called when the plugin is registered with the Flutter engine.
    
    * `FlutterMethodChannel`: Creates a method channel named `"sample_plugin"` for communication between Flutter and native iOS code.
        
    * `registrar.messenger()`: Provides the binary messenger for the channel.
        
    * `SamplePlugin()`: Creates an instance of the `SamplePlugin` class.
        
    * `registrar.addMethodCallDelegate`: Registers the instance as a method call delegate for the channel. This means that the instance will handle method calls coming through the channel.
        

### handle Method

```swift
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
  switch call.method {
  case "getPlatformVersion":
    result("iOS " + UIDevice.current.systemVersion)
  default:
    result(FlutterMethodNotImplemented)
  }
}
```

* `handle` Method: This method handles incoming method calls from Flutter.
    
    * `call.method`: Checks the method name of the incoming call.
        
    * `getPlatformVersion`: If the method name is `"getPlatformVersion"`, it returns the iOS version of the device.
        
    * `result("iOS " + UIDevice.current.systemVersion)`: Sends the result back to Flutter with the iOS version.
        
    * `result(FlutterMethodNotImplemented)`: If the method is not recognized, it returns a "not implemented" response.
        

### Summary

* The `SamplePlugin` class is a Flutter plugin for iOS.
    
* It sets up a `FlutterMethodChannel` for communication between Flutter and native iOS code.
    
* It handles a specific method call (`getPlatformVersion`) to return the iOS version.
    
* It properly registers itself with the Flutter engine and handles method calls through the `handle` method.
    

This code enables Flutter to call native iOS functions and get responses back, which is useful for our platform-specific functionality.

## The Dart API:

Now let's look at the associated dart code provided to us in `lib/sample_plugin_method_channel.dart`

```dart
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';

import 'sample_plugin_platform_interface.dart';

/// An implementation of [SamplePluginPlatform] that uses method channels.
class MethodChannelSamplePlugin extends SamplePluginPlatform {
  /// The method channel used to interact with the native platform.
  @visibleForTesting
  final methodChannel = const MethodChannel('sample_plugin');

  @override
  Future<String?> getPlatformVersion() async {
    final version = await methodChannel.invokeMethod<String>('getPlatformVersion');
    return version;
  }
}
```

### Code Explanation:

```dart
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';

import 'sample_plugin_platform_interface.dart';
```

These are the import statements. The code is importing necessary Flutter packages and a local file (`sample_plugin_platform_interface.dart`).

```dart
/// An implementation of [SamplePluginPlatform] that uses method channels.
class MethodChannelSamplePlugin extends SamplePluginPlatform {
```

This defines a class named `MethodChannelSamplePlugin` that extends `SamplePluginPlatform`. It's an implementation of the platform interface using method channels.

```dart
  /// The method channel used to interact with the native platform.
  @visibleForTesting
  final methodChannel = const MethodChannel('sample_plugin');
```

This creates a `MethodChannel` object named `methodChannel`. Method channels are used for communication between Flutter (Dart) and the native platform (iOS/Android). The channel is identified by the string 'sample\_plugin'.

The `@visibleForTesting` annotation indicates that this field is intended to be visible for testing purposes.

```dart
  @override
  Future<String?> getPlatformVersion() async {
    final version = await methodChannel.invokeMethod<String>('getPlatformVersion');
    return version;
  }
```

This is an overridden method from the `SamplePluginPlatform` class. It's an asynchronous method that returns a `Future<String?>`.

Inside the method:

1. It calls `invokeMethod` on the `methodChannel`, passing 'getPlatformVersion' as the method name.
    
2. This invokes a method on the native side (iOS/Android) through the method channel.
    
3. The result (platform version) is then returned.
    

In summary, this code sets up a method channel to communicate with native code, specifically to retrieve the platform version.

## Example Directory:

The `example/` directory contains a simple Flutter application that depends on the created `sample_plugin`. Let's check out the `pubspec.yaml` file:

```yaml
name: sample_plugin_example
description: "Demonstrates how to use the sample_plugin plugin."
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev

environment:
  sdk: '>=3.4.4 <4.0.0'

dependencies:
  flutter:
    sdk: flutter

  sample_plugin:
    path: ../
  cupertino_icons: ^1.0.6

dev_dependencies:
  integration_test:
    sdk: flutter
  flutter_test:
    sdk: flutter

  flutter_lints: ^3.0.0

flutter:

  uses-material-design: true
```

As we can see it set the path of the sample plugin.

### Using the Plugin:

To use the plugin package, we start by importing it into our Dart libraries like, like any other plugin:

```dart
import 'package:sample_plugin/sample_plugin.dart'
```

Now we can see the `main.dart` under `example/lib/` in the following folder:

```dart
class _MyAppState extends State<MyApp> {
  String _platformVersion = 'Unknown';
  final _samplePlugin = SamplePlugin();

  @override
  void initState() {
    super.initState();
    initPlatformState();
  }

  // Platform messages are asynchronous, so we initialize in an async method.
  Future<void> initPlatformState() async {
    String platformVersion;
    // Platform messages may fail, so we use a try/catch PlatformException.
    // We also handle the message potentially returning null.
    try {
      platformVersion =
          await _samplePlugin.getPlatformVersion() ?? 'Unknown platform version';
    } on PlatformException {
      platformVersion = 'Failed to get platform version.';
    }

    // If the widget was removed from the tree while the asynchronous platform
    // message was in flight, we want to discard the reply rather than calling
    // setState to update our non-existent appearance.
    if (!mounted) return;

    setState(() {
      _platformVersion = platformVersion;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
        ),
        body: Center(
          child: Text('Running on: $_platformVersion\n'),
        ),
      ),
    );
  }
}
```

So we want to make note of some points here:

* The method calling is `async` as platform messages are asynchronous.
    
* Since platform messages may fail, we are using a try/catch `PlatformException` that helps to inspect errors.
    
* `!(mounted)` helps us in discarding the result from the platform if the widget is removed from the tree by then.
    
* We call `setState()` to notify our stateful widget to show the updated platform version from the plugin.
    

### Documentation for your Plugin:

Creating good documentation for your project is out of scope of this article. You can read up more on this from dart website: [Effective Dart Documentation](https://dart.dev/effective-dart/documentation).

Good luck and happy coding 🐦
