RudderStack Logo
  • Product
    • RudderStack Cloud

      Fully managed, scalable and production ready customer data pipelines for your data infrastructure.

    • RudderStack Open Source

      All the core features and integrations that make RudderStack the customer data pipeline of your data infrastructure.

    • Event Stream
    • Warehouse Actions
    • Cloud Extract
  • Learn
    • Blog

      Read articles, feature announcements, community highlights and everything around data.

    • Video Library

      Watch tutorials on how to get the most out of RudderStack and your Customer Data.

    • Migration Guides

      Howtos and best practises for migrating from platforms like Snowplow and Segment to RudderStack.

    • Documentation
    • Segment Comparison
    • Snowplow Comparison
    • Case Studies
  • Integrations
  • Docs
  • Pricing
  • Login
  • Sign up free
Sign up free
Developing a Custom Plugin using Flutter

Developing a Custom Plugin using Flutter

By Sai Venkat Desu/March 04, 2021

Flutter is Google’s free and open-source UI application development toolkit. It is used to build high-quality native interfaces on Android and iOS using a single codebase. One interesting thing about Flutter is that it works with existing code and is used by developers and organizations worldwide. In this post, we will learn how to develop a custom plugin using Flutter.

As a part of our SDK roadmap at RudderStack, we wanted to develop a Flutter SDK. Our existing SDKs include features such as storing event details and persisting user details on the database, and much more. However, these features are already implemented in our Android and iOS SDKs.

The Flutter SDK that we intend to develop is also meant to run on either your Android or iOS devices. So, we wanted to develop a solution in which we can use our existing Android and iOS SDK and develop the Flutter SDK.

All the brainstorming finally led us to the idea of developing a custom plugin in Flutter. The custom plugin follows a basic mechanism based on Flutter’s flexible system that allows calling platform-specific APIs available in Kotlin or Java on Android or Swift or Objective-C code on iOS.

Working of the Flutter SDK Across Different Channels

Flutter’s built-in platform-specific API support does not rely on code generation but rather on a flexible message-passing style using a Platform Channel. To create a custom plugin, let us understand the Flutter architecture in detail:

  • The Flutter portion of the app sends messages to its host - the iOS or Android portion of the app, over a platform channel.
  • The host listens on the platform channel and receives the message. It then calls into any number of platform-specific APIs—using the native programming language—and sends a response back to the client, the app’s Flutter portion, as shown below:

flutterarchitecture
Architectural overview of how platform channels work between different platforms

Building a Custom Plugin using Flutter

Getting Started

The following example demonstrates how to call a platform-specific API to retrieve and display the current battery level. It uses the Android BatteryManager API and the iOS device.batteryLevel API, via a single platform message, getBatteryLevel().

Step 1: Create the Package

To create a plugin package,

  • Use the --template=plugin flag with the Flutter create command.
  • Use the --platforms= option followed by a comma-separated list to specify the plugin supports platforms. Available platforms are Android, iOS, web, Linux, macOS, and Windows.
  • Use the --org option to specify your organization, using reverse domain name notation. This value is used in various package and bundle identifiers in the generated plugin code.
  • Use the -a option to specify the language for Android or the -i option to specify the language for iOS.
  • Below is the example command to create a plugin package for Android, iOS platforms while using java for Android and Objective-C for iOS.

flutter create --org com.rudderstack --template=plugin --platforms=android,ios -a java -i objc batteryLevel
view raw creating_flutter_pachage.js hosted with ❤ by GitHub

  • This command creates a plugin project in the batteryLevel folder with the specialized content given as follows:

    • lib/batteryLevel.dart - The Dart API for the plugin.
    • android/src/main/java/com/rudderstack/batteryLevel/BatteryLevelPlugin.java - The Android platform-specific implementation of the plugin API in Java.
    • ios/Classes/BatteryLevelPlugin.m - The iOS-platform specific implementation of the plugin API in Objective-C.
    • example/ - A Flutter app that depends on the plugin and illustrates how to use it.

Check out how different dart values are received on the platform side and vice versa on the Flutter website.

Step 2: Create the Flutter Platform Client

The app’s State class holds the current app state. Extend that to hold the current battery state.

  • First, construct the channel by using MethodChannel with a single platform method that returns the battery level.
  • The client and host sides of a channel are connected through a channel name that’s passed in the channel constructor.

    Note: All channel names used in a single app must be unique.

  • Prefix the channel name with a unique domain prefix. For example, org.rudderstack.dev/battery.
  • Open the batteryLevel.dart file located in the lib folder.
  • Create the method channel object as shown below with the channel name as org.rudderstack.dev/battery.
  • Please ensure that you are initializing the channel object with the same name as in Flutter across both the Android and iOS platforms.

import 'dart:async';
import 'package:flutter/services.dart';
class BatteryLevel {
static const MethodChannel _channel =
MethodChannel('org.rudderstack.dev/battery');
// Get battery level.
}
view raw create-the_flutter_platform_client.js hosted with ❤ by GitHub

  • Next, invoke a method on the method channel, specifying the concrete method to call using the string identifier getBatteryLevel. For example, the call might fail if the platform does not support the platform API (such as when running in a simulator). So, wrap the invokeMethod call in a try-catch statement.
  • Once you get the battery level, return it using the following code:

// Get battery level.
static Future<String> getBatteryLevel() async {
String batteryLevel;
try {
final int result = await _channel.invokeMethod('getBatteryLevel');
batteryLevel = 'Battery level: $result%.';
} on PlatformException {
batteryLevel = 'Failed to get battery level.';
}
return batteryLevel;
}
}
view raw returning_the_battery_level.js hosted with ❤ by GitHub

  • Now, replace the example/lib/main.dart file to contain a small user interface that displays the battery state in a string and a button for refreshing the value:

import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:batteryLevel/batteryLevel.dart';
void main() {
runApp(MaterialApp(home: MyApp()));
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String _batteryLevel = 'Unknown';
@override
void initState() {
super.initState();
}
// Platform messages are asynchronous, so we initialize in an async method.
Future<void> _getBatteryLevel() async {
String batteryLevel;
// Platform messages may fail, so we use a try/catch PlatformException.
try {
batteryLevel = await BatteryLevel.getBatteryLevel();
} on PlatformException {
batteryLevel = 'Failed to get platform version.';
}
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, and we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return;
setState(() {
_batteryLevel = batteryLevel;
});
}
@override
Widget build(BuildContext context) {
return Material(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
child: Text('Get Battery Level'),
onPressed: _getBatteryLevel,
),
Text(_batteryLevel),
],
),
),
);
}
}
view raw displaying_the_battery_state_in_a_string_and_a_button.js hosted with ❤ by GitHub

Step 3: Add Android Platform-Specific Implementation

Open BatteryLevelPlugin.java within android/src/main/java/com/rudderstack/batteryLevel/ and make the changes as follows:

  • First, change the channel name in the initialization of MethodChannel object to org.rudderstack.dev/battery as follows:

@Override
public void onAttachedToEngine(
@NonNull FlutterPluginBinding flutterPluginBinding
) {
channel =
new MethodChannel(
flutterPluginBinding.getBinaryMessenger(),
"org.rudderstack.dev/battery"
);
channel.setMethodCallHandler(this);
}
view raw changing_the_channel_name.js hosted with ❤ by GitHub

  • Now, replace onMethodCall with the definition shown below to handle the getBatteryLevel call and respond with batteryLevel as follows:

@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
if (call.method.equals("getBatteryLevel")) {
result.success(99);
} else {
result.notImplemented();
}
}
view raw replacing_onmethodcallwith_the_definition.js hosted with ❤ by GitHub

Step 4: Add iOS Platform-Specific Implementation

Open BatteryLevelPlugin.m under ios/Classes/ and make the following changes:

  • First, change the channel name in the initialization of FlutterMethodChannel object to org.rudderstack.dev/battery as follows:

+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
FlutterMethodChannel* channel = [FlutterMethodChannel
methodChannelWithName:@"org.rudderstack.dev/battery"
binaryMessenger:[registrar messenger]];
BatteryLevelPlugin* instance = [[BatteryLevelPlugin alloc] init];
[registrar addMethodCallDelegate:instance channel:channel];
}
view raw changing_the_channel_name_in_fluttermethodchannel.js hosted with ❤ by GitHub

  • Next, replace the handleMethodCall method with the definition below to handle the getBatteryLevel call and respond with batteryLevel as follows:

- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
if ([@"getBatteryLevel" isEqualToString:call.method]) {
result(@(99));
} else {
result(FlutterMethodNotImplemented);
}
}
view raw replace_the_handlemethodcall_method_with_definition.js hosted with ❤ by GitHub

With this, we have successfully developed a custom plugin. Now you can run the plugin across any two platforms (Android and iOS) and understand how it works.

Publishing the Custom Plugin

Let’s quickly look at a few instructions that you need to keep in mind after developing the custom plugin:

  • After developing the custom plugin, you can publish the custom plugin at pub.dev so that other developers can easily use it. However, before publishing, review the pubspec.yaml, README.md, CHANGELOG.md, and LICENSE files to ensure that the content is complete and correct.
  • Next, run the publish command in the dry-run mode to see if everything passes the analysis:

$ flutter pub publish —dry-run

  • The next step is publishing to pub.dev, but ensure that you are ready because publishing is a final step that cannot be reverted:

$ flutter pub publish

For more details on publishing, check out the publishing docs on dart.dev.

References:

  • https://flutter.dev/docs/development/packages-and-plugins/developing-packages
  • https://flutter.dev/docs/development/platform-integration/platform-channels?tab=android-channel-java-tab

You can also check out the following Github repositories:

  • Sample App created in this post.
  • Rudderstack’s Flutter SDK

You can download Rudderstack`s Flutter SDK as a dependency for your Flutter Apps from pub.dev.

If you’re interested in app development, check out this post from G2i on React Native to learn more about the most important skills and attributes for React Native developers.

Explore More With RudderStack

We currently support over 20 sources, including Warehouse Actions, Cloud Extracts, and Event streams. We also support over 80 destinations and add a new one every two weeks, so be sure to look at the other integrations we support. Sign up with RudderStack Cloud Free to try out these integrations for free!

Join our Slack to chat with our team, explore our open source repos on GitHub, and follow us on social: Twitter, LinkedIn, dev.to, Medium, YouTube. Don’t miss out on any updates. Subscribe to our blogs today!

Sai Venkat Desu
Sai Venkat Desu
Desu is a Software Engineer in the Integration Team at RudderStack. He develops on Android and iOS SDKs and device-mode Integrations using ReactNative and Flutter.

Recent Posts

Choosing the Best Tool for Mobile Attribution: Kochava, AppsFlyer, Adjust, Branch
Choosing the Best Tool for Mobile Attribution: Kochava, AppsFlyer, Adjust, Branch
By Ruchira Moitra/March 28, 2021
Modern businesses are heavily reliant on multi-channel strategies such as marketing campaigns, targeted messaging, etc., to…
Read More →
Build or Buy? Lessons From Ten Years Building Customer Data Pipelines
Build or Buy? Lessons From Ten Years Building Customer Data Pipelines
By Soumyadeb Mitra/November 19, 2020
Before RudderStack, I tried to build customer data pipelines inside a large enterprise using homegrown and vendor solutions. This…
Read More →
Customer Data Pipelines Play a Key Role in Data Privacy
Customer Data Pipelines Play a Key Role in Data Privacy
By Gavin Johnson/March 07, 2021
Customer data pipelines play a critical role in the privacy of your customer data. They are one of the primary and most expansive…
Read More →

Subscribe

We'll send you updates from the blog and monthly release notes.

Explore RudderStack Today


⚡ Our Free plan includes 500,000 events per month so you can explore and test the product.

Install an SDK, connect a destination, and see data start to flow.


Sign up free

Company

  • About
  • Contact Us
  • We're Hiring!
  • Privacy Policy
  • Terms of Service

Product

  • RudderStack Cloud
  • Open Source
  • Segment Comparison
  • Snowplow Comparison

Resources

  • Blog
  • Video Library
  • Documentation
  • Slack Community
  • The DataStack Show Podcast

JOIN THE CONVERSATION

Learn more about the product and how other engineers are building their customer data pipelines.

Join our Slack Community

READ OUR DOCUMENTATION

Technical documentation on using RudderStack to collect, route and manage your event data securely.

Go to docs
RudderStack Logo
© RudderLabs Inc.