Develop a Flutter plugin step by step for iOS and Android

Published on

You may have found yourself searching for a flutter plugin on the largest library-sharing platform, pub.dev. Yet, despite your persistence in scrolling through the list, nothing seems to match your search... An idea suddenly crosses your mind...

What if it were YOU who would create that elusive plugin that only your mind is capable of imagining?

In addition to creating a plugin that will be useful for your wonderful applications, get ready to help others in the future 😇
At first glance, creating a Flutter plugin can seem intimidating.

In this article, we will break down this prejudice and show you that creating one is simple 👍.
This tutorial has been written with the aim of teaching you how to easily create a Flutter plugin that will allow you to retrieve battery information 🔋 on iOS (Swift) and Android (Kotlin) only.

Why create a Plugin / Package?

What we are going to create is a library that will allow us to encapsulate a maximum amount of logic and/or communicate directly with the device on the native side.

Flutter plugin library

The main advantage of using a library is to make code reusable, allowing anyone to easily integrate our library into their own project, and thanks to the wonderful world of open source, contribute to its development and maintenance.

To get started, we first need to distinguish between a Plugin and a Package. Both may seem similar, but there are differences...

What the difference between a flutter Package and a plugin?

Let's start with the Package.
A Package contains ONLY Dart code and nothing more. It doesn't allow for any native calls. Here are some examples of packages: bloc, mockito, http, lint, and so on.

What is a Plugin?

A Plugin, on the other hand, contains both Dart code and native code. The native code enables communication with the APIs of the device running the application.

This requires a minimum level of knowledge in the following languages:

Platform Languages
iOS Objective-C ou Swift
macOS Objective-C ou Swift
Android Java ou Kotlin
Windows C++
Linux C++
Web HTML/CSS/JS

It's worth noting that you can choose the development language for iOS, Android, and macOS.

For example, if you are more comfortable with Objective-C, it's entirely possible to create a plugin entirely using that language for the iOS and macOS parts. Here are some examples of plugins: image_picker, flutter_native_splash, camerawesome, cached_network_image, etc.

Choosing Between Package or Plugin?

If your library doesn't communicate with the native side, then go with a Package.
If you plan to communicate with the native part of the device, then a Plugin is the best choice.
As a reminder, this tutorial will only cover the creation of a Plugin, as creating a package is relatively similar, with the native part omitted.

Let's Get Started 🤗!

Create a Flutter plugin step by step

  1. Create the project.
  2. Implement Dart-side methods.
  3. Implement tests.
  4. Implement iOS.
  5. Implement Android.
  6. Use the plugin in the example.

Creating the Project 😎

Execute the following command to create a new plugin project for iOS and Android:

flutter create --template=plugin --platforms=android,ios mybatteryplugin

flutter create plugin command

Note ℹ️: By default, Swift is used for iOS and Kotlin for Android. If you want to use another language, you can add the arguments -i objc to use Objective-C and -a java to use Java.

files in flutter plugin

Open the newly created project in your favorite code editor (we will use VSCode here).

Several files are available, and here's a brief explanation of the purpose of the files you will need during development:

Implementing Dart-side methods of your plugin

abstract class MybatterypluginPlatform extends PlatformInterface {
  
  // [...]

  Future<num?> getBatteryLevel() {
    throw UnimplementedError('getBatteryLevel() has not been implemented.');
  }
}

We will now define the battery retrieval method. To do so, open the file lib/mybatteryplugin.dart. This method will be visible once the plugin is imported into the project.

class Mybatteryplugin {
  // [...]

  /// récupération du niveau de la batterie
  Future<num?> getBatteryLevel() {
    return MybatterypluginPlatform.instance.getBatteryLevel();
  }
}

Note ℹ️: It is highly recommended to add a comment with three "///" slashes to specify the role of your method.

And finally, we will edit the file lib/mybatteryplugin_method_channel.dart and insert the final implementation that will use the method channel to communicate with the native side.

class MethodChannelMybatteryplugin extends MybatterypluginPlatform {
  
  // [...]

  
  Future<num?> getBatteryLevel() {
    return methodChannel.invokeMethod<num?>('getBatteryLevel');
  }
}

We're done with the Dart side, wasn't that easy? 😎

easy

Implementing tests for your plugin 🧪

It's always a good idea to test your plugin to make sure it works as expected. A good application is an application that is tested! And so is a good plugin!

To do this, we will use the test folder.

Attention 🚨: By default, the template already includes an example based on the method getPlatformVersion(), which will be entirely removed from the project in this tutorial.

Open the file test/mybatteryplugin_test.dart (it should be highlighted in red) and start by adding, at the top of the file, the mock related to our new method, and then implement the test in the main() method:

class MockMybatterypluginPlatform
    with MockPlatformInterfaceMixin
    implements MybatterypluginPlatform {

  // [...]
  
  // Here, we're overriding the getBatteryLevel() method to return a fixed value of 21, 
  // which will be tested in the following test.
  
  Future<num?> getBatteryLevel() => Future.value(21);
}

void main() {

  // [...]

  // We're creating a new test to verify if the previously overridden value is returned correctly.
  test('getBatteryLevel', () async {
    Mybatteryplugin mybatterypluginPlugin = Mybatteryplugin();
    MockMybatterypluginPlatform fakePlatform = MockMybatterypluginPlatform();
    MybatterypluginPlatform.instance = fakePlatform;

    expect(await mybatterypluginPlugin.getBatteryLevel(), 21);
  });
}

Now, open the file test/mybatteryplugin_method_channel_test.dart and modify it to replace the mock and the associated new test:

void main() {
  // [...]

  setUp(() {
    // We will add a new mock related to the Method Channel object
    // This will return the value of 21.
    channel.setMockMethodCallHandler((MethodCall methodCall) async {
      if (method.call == 'getBatteryLevel') {
        return 21;
      }
    });
  });

  // [...]

  test('getBatteryLevel', () async {
    expect(await platform.getBatteryLevel(), 21);
  });
}

Everything is okay 🤗, you should be able to run the command flutter test successfully!

Implementing iOS side of your plugin 🍏

Now, we need to run the installation of dependencies for the Xcode project using CocoaPods:

cd example/ios/
pod install --repo-update

pod install

Note ℹ️: If you don't have CocoaPods installed, you can follow the instructions on the official website: https://cocoapods.org/

From this point on, we will be developing in Swift from Xcode.
However, it's also possible to continue using VSCode or other IDEs.
Now, open the file example/ios/Runner.xcworkspace with Xcode.

open Runner.xcworkspace

Attention 🚨: Make sure to open the .xcworkspace file and not the .xcodeproj file!

Note ℹ️: By opening the example project, you will be able to run your plugin on the sample project and debug the native code from Xcode, which is quite convenient!

In the sidebar, expand "Pods," do the same for "Development Pods," and so on for "mybatteryplugin."
Expand ".." and repeat the process by expanding the first item each time until you reveal 3 files.

Open the file SwiftMybatterypluginPlugin.swift

Xcode project

We will now add a switch statement inside the handle() method to detect a call to our method:

// [...]

public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
  switch (call.method) {
    case "getBatteryLevel":
      // code natif
    default:
      result(FlutterError(code: "HANDLE_ERROR", message: "method not implemented", details: nil))
      break
    }
}

Modify the code that will allow us to retrieve the battery level:

// [...]

public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
  switch (call.method) {
    case "getBatteryLevel":
      // il est nécessaire d'activer le suivi de la batterie avant
      UIDevice.current.isBatteryMonitoringEnabled = true
      
      // we return the battery level in percent
      result(UIDevice.current.batteryLevel * 100)
    default:
      result(FlutterError(code: "HANDLE_ERROR", message: "method not implemented", details: nil))
      break
    }
}

The iOS side of our flutter plugin is now complete! 🍏

ios done

Implementing Android side of your flutter plugin 🤖

Now, we will implement the Android side of our plugin.

In this section, we will use Android Studio, but it is entirely possible to continue using VSCode.

Open the project example/android/ using Android Studio.

Note ℹ️: By opening the example project, you will be able to run your plugin on the sample project and debug the native code from Android Studio, which is essential!

android studio flutter plugin files

In the sidebar, expand mybatteryplugin, then java, and finally com.example.mybatteryplugin.

Open the file MybatterypluginPlugin.kt

android studio flutter plugin files

We will now replace the contents of the onMethodCall() method with a switch statement to detect a specific call to our method:

// [...]

override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
    when (call.method) {
      "getBatteryLevel" -> {
        // native code
      }
      else -> {
        result.notImplemented()
      }
    }
  }

Let's add the battery manager that will allow us to retrieve the battery level later, and don't forget to import the dependencies.

class MybatterypluginPlugin: FlutterPlugin, MethodCallHandler {
  private lateinit var channel : MethodChannel
  private lateinit var batteryManager: BatteryManager // add this line

  // [...]
}

We will now instantiate the battery manager using the context from the plugin in the onAttachedToEngine() method.

// [...]

override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
    channel = MethodChannel(flutterPluginBinding.binaryMessenger, "mybatteryplugin")
    channel.setMethodCallHandler(this)
    
    // Initialisation du battery manager
    batteryManager = flutterPluginBinding.applicationContext.getSystemService(Context.BATTERY_SERVICE) as BatteryManager
  }

// [...]

All that's left is to add the piece of code that will retrieve the battery level from the manager:

// [...]

override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
    when (call.method) {
      "getBatteryLevel" -> {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
          result.success(batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY));
        } else {
          result.error("WRONG_VERSION", "android version not supported", "");
        }
      }
      else -> {
        result.notImplemented()
      }
    }
  }

// [...]

The Android side of our flutter plugin is now complete! 🤖

android done

Using the plugin in the example 📱

Now that our plugin is complete, we will use it in the example project to test it.

The long-awaited moment is approaching! We can finally test our plugin 🤩

To do this, open the example project located in the example folder with VSCode.

example project creating flutter plugin

Edit the file lib/main.dart and import your plugin (usually it's already imported):

import 'package:mybatteryplugin/mybatteryplugin.dart';

In the default stateful widget created, we will instantiate and use the plugin:

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  // plugin instanciation 
  final _mybatterypluginPlugin = Mybatteryplugin();

  // this is where we will store the battery level
  num? _batteryLevel;

  
  void initState() {
    super.initState();

    // execute the method to retrieve the battery level
    _mybatterypluginPlugin.getBatteryLevel().then((batteryLevel) {
      setState(() {
        _batteryLevel = batteryLevel;
      });
    });
  }

  
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          // we display the battery level
          child: _batteryLevel != null
              ? Text('Niveau de la batterie: $_batteryLevel')
              : const CircularProgressIndicator(),
        ),
      ),
    );
  }
}

It's now complete! 🤩

android done

Bonus Scoring your plugin on pub.dev

A plugin/package is deployed on pub.dev. Google highlights libraries that are well maintained with good code quality.

To ensure your new plugin is well-referenced, it's important to pay attention to the score it's assigned. This score is based on a total of 140 "pub points," with the goal, of course, being to obtain as many points as possible.

pub points pub dev

This score is calculated once the package is published and analyzed by a Google robot.

To optimize our chances, we will use the pana tool, which calculates the score before publication, a great help, isn't it? 👍

Execute the following command to install pana.

dart pub global activate pana

Go to the root of your project, and then run the analysis with pana.

dart pub global run pana .

pub points pub dev

It's up to you to fix any issues (if there are any) and get the maximum points!

Note ℹ️: pana can modify your code, so be sure to create a git stash before running it.

Publish your plugin on pub.dev

CAs seen earlier, pub.dev is the platform that will allow you to easily distribute your plugin to thousands of developers for free.

Log in with your Google account on pub.dev.

publish result

Execute the following command to test your publication before publishing it:

flutter pub publish --dry-run

Now that everything is ready, we can publish our plugin on pub.dev:

flutter pub publish

publish

Conclusion

You have just created your first plugin for iOS and Android, allowing you to interact with the device's native features.

Now it's your turn to come up with the best plugin idea and let your imagination run wild 😁

Sources

Create a 5 stars app using our Flutter templates

Check our flutter boilerplate
kickstarter for flutter apps
Read more
You may also be interested in
Our Flutter Template is Here!  blog card image
Our Flutter Template is Here!
Published on 2023-10-10
Flutter theme made easy  blog card image
Flutter theme made easy
Published on 2023-10-23
ApparenceKit is a flutter template generator tool by Apparence.io © 2024.
All rights reserved