Skip to content

Implement a plugin

Pierre Champion | Drakirus edited this page Nov 4, 2019 · 20 revisions

The source code of this example is available in the example repo.

Implement a plugin

Flutter uses a flexible system that allows you to call platform-specific APIs, make sure to be familiar with the way Flutter handles platform calls on Android or iOS before going further. A great tutorial is available on flutter.dev.

Example: Calling platform-specific Golang code using platform channels.

The following code demonstrates the matching Go implementation described in the official Flutter platform-channels docs
The goal is to retrieve the current battery level of the device via a single platform message, getBatteryLevel.

Step 1: Create the Flutter platform client

Matching step in the official Flutter docs

We use a MethodChannel with a single platform method that returns the battery level.

// File: lib/main_desktop.dart
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';

// The client and host sides of a channel are connected through 
// a channel name passed in the channel constructor.
const platform = const MethodChannel('samples.flutter.dev/battery');

void main() {
  test('Test Battery Plugin result', () async {
    // Invoke a method on the method channel, specifying the concrete
    // method to call via the String identifier.
    final int result = await platform.invokeMethod('getBatteryLevel');
    expect(result, 55);

    final String batteryLevel = 'Battery level at $result % .';
    print(batteryLevel);

    SystemNavigator.pop(); // close the app
  });
}

Step 2: Add an platform-specific implementation using Golang

The following code implementation the Golang platform channels.
The package name chosen is battery, making the Plugin available by accessing &battery.MyBatteryPlugin{}

// File: main.go
package battery

import (
	flutter "github.com/go-flutter-desktop/go-flutter"
	"github.com/go-flutter-desktop/go-flutter/plugin"
)

//  Make sure to use the same channel name as was used on the Flutter client side.
const channelName = "samples.flutter.dev/battery"

// MyBatteryPlugin demonstrates how to call a platform-specific API to retrieve
// the current battery level.
//
// This example matches the guide example available on:
// https://flutter.dev/docs/development/platform-integration/platform-channels
type MyBatteryPlugin struct{}

var _ flutter.Plugin = &MyBatteryPlugin{} // compile-time type check

// InitPlugin creates a MethodChannel and set a HandleFunc to the
// shared 'getBatteryLevel' method.
// https://godoc.org/github.com/go-flutter-desktop/go-flutter/plugin#MethodChannel
//
// The plugin is using a MethodChannel through the StandardMethodCodec.
//
// You can also use the more basic BasicMessageChannel, which supports basic,
// asynchronous message passing using a custom message codec. 
// You can also use the specialized BinaryCodec, StringCodec, and JSONMessageCodec 
// struct, or create your own codec.
//
// The JSONMessageCodec was deliberately left out because in Go its better to
// decode directly to known structs.
func (p *MyBatteryPlugin) InitPlugin(messenger plugin.BinaryMessenger) error {
	channel := plugin.NewMethodChannel(messenger, channelName, plugin.StandardMethodCodec{})
	channel.HandleFunc("getBatteryLevel", handleGetBatteryLevel)
	return nil // no error
}

// handleGetBatteryLevel is called when the method getBatteryLevel is invoked by
// the dart code.
// The function returns a fake battery level, without raising an error.
//
// Supported return types of StandardMethodCodec codec are described in a table:
// https://godoc.org/github.com/go-flutter-desktop/go-flutter/plugin#StandardMessageCodec
func handleGetBatteryLevel(arguments interface{}) (reply interface{}, err error) {
	batteryLevel := int32(55) // Your platform-specific API
	return batteryLevel, nil
}

In case you aren't using a hover compatible plugin, click here 👇

If you are using a hover compatible plugin, the following steps are already automated for you.

Before adding this plugin to go-flutter, we need to setup few requirements:

  • go-flutter uses Go modules. Make sure your go version is updated.
  • A go.mod and go.sum file.

Next to the above main.go file, generate the go.mod and go.sum files using:

# enable go modules
export GO111MODULE=on                                           
# create the go.mod
go mod init github.com/go-flutter-desktop/plugins/go-plugin-example/battery
# add missing modules
go mod tidy                                                     

Outputs:

// File: Generated go.mod
module github.com/go-flutter-desktop/plugins/go-plugin-example/battery

go 1.12

require github.com/go-flutter-desktop/go-flutter vX.XX.X

Your plugin is DONE!

Step 3: Link your home made plugin to go-flutter

First, read the Plugin info wiki section.
Assuming you are using hover, and you have initialized hover in your Flutter project, edit the option.go file and add your local plugin.

Our option.go file is looking like this:

// File: go/cmd/options.go
package main

import (
	"github.com/go-flutter-desktop/go-flutter"
	// url set in `go mod init 'url'`
	  "github.com/go-flutter-desktop/plugins/go-plugin-example/battery"
)

var options = []flutter.Option{
	flutter.WindowInitialDimensions(200, 200),
	flutter.PopBehavior(flutter.PopBehaviorClose), // on SystemNavigator.pop() closes the app
	flutter.AddPlugin(&battery.MyBatteryPlugin{}), // our plugin
}

Step 4: Reference your remote plugin as a local one

To fetch the dependency, hover uses the go.mod located under the go/ directory of your Flutter project.
Since there is no go module available at the github.com/go-flutter-desktop/plugins/go-plugin-example/battery url.
Our build will fail.

$ hover run
go: github.com/go-flutter-desktop/plugins/go-plugin-example/[email protected]: unknown revision go-plugin-example/v0.1.0
go: error loading module requirements

To test before publishing, we can use the replace directive in /go/go.mod.
Our go.mod file now looks something like this:

// File ./go/go.mod
module github.com/go-flutter-desktop/examples/plugin_tutorial/desktop

go 1.12

require (
	github.com/go-flutter-desktop/go-flutter vX.XX.X
	github.com/go-flutter-desktop/plugins/go-plugin-example/battery v0.1.0
)

replace github.com/go-flutter-desktop/plugins/go-plugin-example/battery => ../go-plugin-example/battery

Go wont try to fetch MyBatteryPlugin at the github.com/go-flutter-desktop/plugins/go-plugin-example/battery VCS url, instead it will use one located on your file filesystem.

The new import path from the replace directive is used without needing to update the import paths in the actual source code.


$ hover run
flutter: Observatory listening on http://127.0.0.1:50300/GAkYtsSwdYU=/
flutter: 00:00 +0: Test Battery Plugin result
flutter: Battery level at 55 % .
flutter: 00:00 +1: All tests passed!
go-flutter: closing application
app 'plugin_tutorial' exited.

🎊 YaY 🎊
We can now publish the platform-specific Go code as a remote Go module.

Wanting more?

  • You can read existing implementations, path_provider is a good example.
  • For more informations, read the our docs about Plugin: here and here.

Call Dart from Golang

The previous article explained how to expose a platform (Golang) service in your host app code and have it invoked from the Dart side.
The Flutter plugin API also support calling Dart handler from Golang.

Using InvokeMethod on a MethodChannel triggers the corresponding Dart MethodCallHandler.

Example:

// Golang
channel := plugin.NewMethodChannel(messenger, "samples/demo", plugin.StandardMethodCodec{})
err := p.channel.InvokeMethod("test", nil)
// error handling..
// Dart
const platform_channel = MethodChannel("samples/demo");
platform_channel.setMethodCallHandler((MethodCall methodCall) async {
  print(methodCall.method); // prints: test
});

if you wish to have a reply use InvokeMethodWithReply, example:

// Golang
channel := plugin.NewMethodChannel(messenger, "samples/demo", plugin.StandardMethodCodec{})
reply, err := channel.InvokeMethodWithReply("test", nil) // blocks the goroutine until reply is avaiable
// error handling..
spew.Dump(reply) // prints 5 seconds later: (string) (len=21) "reply from dart: test"
// Dart
const platform_channel = MethodChannel("samples/demo");
platform_channel.setMethodCallHandler((MethodCall methodCall) async {
  print(methodCall.method); // prints: test
  await Future.delayed(Duration(seconds: 5));
  return "reply from dart: " + methodCall.method;
});

When the platform (Golang) needs to send data every time a particular event happens (e.g. wifi connection changes), the EventChannel named channel can be used.
Example of a go-flutter plugin using the EventChannel is available in the xbox_controller example.