Mastering Flutter: Create a plugin
Tags: flutter, tutorial, dart, mobile
Published: 2021-03-03 16:07:25

This story starts when one of your clients or a designer asks you to implement a feature that can be done using native APIs but it has not included on Flutter yet.

The first thing that you try to do a little search on [pub.dev](https://pub.dev), someone has been in my situation and has developed a package to resolve this situation and has been kind enough to share it with the community. But unfortunately you don't find anything that solves your problem, you have only one solution left: **TO CREATE A PLUGIN**.

But wait, first we should tell you what is a plugin. There are two types of packages in the Flutter ecosystem: 

- Dart packages: general packages with only dart code (e.g. the [path](https://pub.dev/packages/path) package)
- Plugin packages: special packages that combine native code with a dart interface and allow you to use platform-specific code (e.g. the [url_launcher ](https://pub.dev/packages/url_launcher)package)

So...let's do a plugin

If you're using Android Studio you can go under **File -> New -> New Flutter Project...** and then select **Flutter Plugin**

 
![Android Studio menu](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vkfdkfoyp1afq8w1269y.jpeg)


![Flutter Plugin](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8vgubb3k0ll0xfsxngbp.jpeg)
 

You can also create a plugin using the command line:


```
flutter create --template=plugin
```


You can also specify the platform for which you want to develop the plugin and the language you will use 

```
flutter create --org com.example --template=plugin --platforms=android,ios -I swift -a kotlin flutter_is_awesome
```

This will create a plugin called `flutter_is_awesome` with the iOS part in Swift and the Android part in Kotlin.

We will create a plugin that will show the system contacts picker, 

# Plugin Anatomy

This is what will be created:

![Structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jikue1zd2zp6v5uz8juw.jpeg)

We have:

- The `android` folder: we will put all the Android specific code here.
- The `iOS` folder: the same as above, but for iOS.
- The `lib` folder: this folder will contain all the Dart part of the plugin, that will be invoked by the application.
- The `test` folder: Here you will develop the tests for your plugin.
- The `example` folder: An application that imports our package as dependency, you can use this to try out the plugin without importing it in another app.

The plugin uses a a channel called `MethodChannel` to communicate between the Dart part and the native part, it's like a tunnel where the dart part sends messages and the native part listen to those messages and react to them. It can be used also to send messages from the native part to the dart part, but this will not be covered in this article.

Our plugin will have just one function that will show the native contacts picker, let the user choose one contact, and return the name of the contact selected to the user. 

In this example we will return a simple string to the Dart part, but you can return more complex data structures using for example a map, or a list, you can find the list of the data type supported by the plugins [here](https://flutter.dev/docs/development/platform-integration/platform-channels#codec).

Now that we've created the plugin and defined what it will do we need to do those those things:

- Write the Android part of the plugin
- Write the iOS part of the plugin
- Write the dart part of the plugin

# Android Code

When the plugin has been created the default class has been generated, it contains two methods: `onAttachedToEngine` and `onMethodCall`. The first one is called when the plugin is initialized, it creates the channel of communication with the dart part and begins to listen to the channel for messages. The second is invoked whenever there's a new message in the channel; it has 2 parameters: `call` which contains the details of the invocation (the method and eventual parameters) and `result` that will be used to send the result back to the dart part.

```kotlin
class FlutterIsAwesomePlugin: FlutterPlugin, MethodCallHandler {

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

  override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
    if (call.method == "getAContact") {

    } else {
      result.notImplemented()
    }
  }
```

Let's define some variable for our plugin that will be use later

```kotlin
  val PICK_CONTACT_RESULT_CODE = 36
  var act: android.app.Activity? = null
  private lateinit var channel : MethodChannel
  private lateinit var result: Result
```

Now we need to implement the `ActivityAware` and `PluginRegistry.ActivityResultListener `protocols, we need those to retrieve the activity that will be used to show our contact picker.

```kotlin
  override fun onAttachedToActivity(binding: ActivityPluginBinding) {
    act = binding.activity
    binding.addActivityResultListener(this)
  }

  override fun onDetachedFromActivityForConfigChanges() {
    act = null;
  }

  override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
    act = binding.activity
    binding.addActivityResultListener(this)
  }

  override fun onDetachedFromActivity() {
    act = null;
  }

  override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
    return false
  }
```

The only part that's missing is the code to show the contacts picker and retrieve the result. In our `onMethodCall` start the activity to choose a contact and in `onActivityResult `retrieve the result and send it to the dart part using the result object that we've previously saved.

```kotlin
  override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
    this.result = result //saves the result to call it when the user selects a contact
    if (call.method == "getAContact") {
      val intent = Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI)
      act?.startActivityForResult(intent, PICK_CONTACT_RESULT_CODE)
    } else {
      result.notImplemented()
    }
  }

...

  override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
    if (requestCode == PICK_CONTACT_RESULT_CODE) {
      if (resultCode == Activity.RESULT_OK) {
        if (data != null) {
          val contactData = data.data
          val c = act!!.contentResolver.query(contactData!!, null, null, null, null)
          if (c!!.moveToFirst()) {
            val name = c.getString(c.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME))
            result.success(name)
            return true
          }
        }
      }
    }
    return false
  }
```

The Android part is done! **✅** 

# iOS Code

The iOS part will be very similar to the Android one, here we have the `register` function, which is the equivalent of the `onAttachedToEngine` that we've seen for Android, and the `handle` function, that will be called when new messages will arrive.

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

  public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
    if call.method == "getAContact" {

    } else {
        result(FlutterMethodNotImplemented)
    }
  }
}
```

Now we need to create a new class `ContactPickerDelegate` that will receive the callback of the contacts picker view controller (when a contact is selected or when the picker is dismissed) and will inform the plugin class. It has 2 blocks variable `onSelectContact` and `onCancel`that will be invoked when the picker will inform this class with the `CNContactPickerDelegate` protocol.

```swift
import Foundation
import ContactsUI

class ContactPickerDelegate: NSObject, CNContactPickerDelegate {
    public var onSelectContact: (CNContact) -> Void
    public var onCancel: () -> Void

    init(onSelectContact: @escaping (CNContact) -> Void,
         onCancel: @escaping () -> Void) {
        self.onSelectContact = onSelectContact
        self.onCancel = onCancel
        super.init()
    }
    
    func contactPicker(_ picker: CNContactPickerViewController, didSelect contact: CNContact) {
        picker.presentingViewController?.dismiss(animated: true, completion: nil)
        
        onSelectContact(contact)
    }
    
    func contactPickerDidCancel(_ picker: CNContactPickerViewController) {
        picker.presentingViewController?.dismiss(animated: true, completion: nil)
        onCancel()
    }
}
```

What we need to do now is to display the contacts picker when the `getAContact` message is received, so let's add this to the plugin class:

```swift
  public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
    if call.method == "getAContact" {
        getAContact(withResult: result)
    } else {
        result(FlutterMethodNotImplemented)
    }
  }
   
   //save the contact picker, so it's not deallocated    
    var contactPickerDelegate: ContactPickerDelegate?
    private func getAContact(withResult result: @escaping FlutterResult) {
        let contactPicker = CNContactPickerViewController()
        contactPickerDelegate = ContactPickerDelegate(onSelectContact: { contact in
            //sends the result back to dart
            result(contact.givenName + contact.familyName)
            self.contactPickerDelegate = nil //set to nil, so it's removed from memory
        },
        onCancel: {
            result(nil)
            self.contactPickerDelegate = nil //set to nil, so it's removed from memory
        })
        contactPicker.delegate = contactPickerDelegate
        let keyWindow = UIApplication.shared.windows.first(where: { $0.isKeyWindow })
        let rootViewController = keyWindow?.rootViewController
        DispatchQueue.main.async {
            rootViewController?.present(contactPicker, animated: true)
        }
    }
```

The iOS part is done! **✅** 

# Dart Code

The last piece of the puzzle 🧩 is to create the Dart part of the plugin that will glue all the other pieces toghether.

```dart
class FlutterIsAwesome {
  static const MethodChannel _channel =
      const MethodChannel('flutter_is_awesome');

  static Future<String> getAContact() async {
    final String contact = await _channel.invokeMethod('getAContact');
    return contact;
  }
}
``` 

As you can see it's very simple, it invokes the method `getAContact` on the channel and waits for the result and returns it. Note that all the functions that interact with the native part are asynchronous and will return a `Future`.

Now we only need to test out our plugin, we've done a simple app in the example folder, with just one button and a label to test out if everything is working fine.

```dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_is_awesome/flutter_is_awesome.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  String _contact = 'Unknown';

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Flutter is Awesome'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              MaterialButton(
                color: Colors.red,
                textColor: Colors.white,
                child: Text('Picker'),
                onPressed: () => _getAContact(),
              ),
              Text(_contact ?? '')
            ],
          ),
        ),
      ),
    );
  }

  _getAContact() async {
    String contact;
    try {
      contact = await FlutterIsAwesome.getAContact();
    } on PlatformException {
      contact = 'Failed to get contact.';
    }
    if (!mounted) return;
    setState(() {
      _contact = contact;
    });
  }
}
```

And here's the final result:

![Android](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7v4zackfyrpii62c0kkr.gif)

![iOS](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ie8sv65ujd5uqrod9n19.gif)

# Conclusions

You can find the final code of the plugin at this [GitHub link](https://github.com/EnricoOri/FlutterIsAwesome-ContactPicker).

What we've developed here is a basic example that returns just a string to the dart part but, as said before you could return a more complex data structure and you can also pass parameters to the native part when calling the `invokeMethod` functions.
The things that you can do with the plugins are almost infinite, we've just scratched the surface, the limit is your imagination.