2022/5/23/Flutter/How to write a Flutter pluginの訳パート2


<< パート1へ

5. Add the platform-specific code

Now, you provide the platform-specific implementations for a synthesizer that makes sounds when pressing keys on the keyboard. You can think of this code as the library you will surface to Flutter. Often, when making a plugin, you already have a defined platform API that you’ll work from, as in this case.


You now have two separate implementations of the same functionality, one for iOS and one for Android. You need to get these compiling as part of your app so that the plugin can call into it.


Add to iOS

Add the following files to your project:


By placing these files in the ios/Classes location, they will compile as part of the iOS build for your plugin.

これらのファイルを ios/Classes に配置することで、プラグインの iOS ビルドの一部としてコンパイルされます。


You can look at ios/plugin_codelab.podspec to see that, by default, it uses globs to define which sources to compile. All you need to do is put the files in the right location.

ios/plugin_codelab.podspec を見ると、デフォルトでは、どのソースをコンパイルするかを定義するためにグロブを使用していることがわかります。必要なのは、ファイルを正しい場所に置くことだけです。

Add to Android

Add the following file to your project:



By placing this Java file in the android/src/main/java/com/example location, it will compile as part of the Android build for your plugin. You can look at the Gradle build system to see that you only need to place the file in the correct directory to get it to compile.


Synthesizer interface explanation

The synthesizer interface is similar on iOS and Android, and consists of four methods:


class Synthesizer {
  void start();
  void stop();
  int keyDown(int key);
  int keyUp(int key);

The keyUp() and keyDown() methods represent the events sent when a key on the musical keyboard is pressed down and released.

keyUp() と keyDown()メソッドはミュージカルキーボードの鍵盤が「押下された時」と「離された時」のイベントを表しています。


The key argument represents which key is being pressed or released.



It’s an enumeration of all the keys on the musical keyboard.



The MIDI standard defines an enumeration for those keys, where 60 is the value for Middle Cand increments one for every black or white key ( semitone). Your plugin uses this definition.

MIDI規格では、これらのキーの列挙が定義されており、60はMiddle Cの値で、黒鍵、白鍵(半音)ごとに1ずつ増加します。プラグインはこの定義を使用しています。

6. Design the plugin API

The next step in making a plugin is thinking about what sort of information you want to send back and forth between Flutter and the host platform.



If you’re trying to represent a library that already has an API defined, you can make your life easy, and mimic that interface.



In this codelab, we provide the synthesizer code for each platform, so you can mimic its interface in the Dart code:



import 'dart:async';

import 'package:flutter/services.dart';

class PluginCodelab {
  static const MethodChannel _channel = const MethodChannel('plugin_codelab');

  static Future<String?> get platformVersion async {
    final String? version = await _channel.invokeMethod('getPlatformVersion');
    return version;

  static Future<int?> onKeyDown(int key) async {
    final int? numNotesOn = await _channel.invokeMethod('onKeyDown', [key]);
    return numNotesOn;

  static Future<int?> onKeyUp(int key) async {
    final int? numNotesOn = await _channel.invokeMethod('onKeyUp', [key]);
    return numNotesOn;

Notice that the second parameter to invokeMethod() lists the parameters that are sent to the method call.

invokeMethod()の第二引数のリストはmethod callに渡されるパラメーターです。

7. Implement the plugin platform code

Now you have platform-specific libraries for making sound and Dart code that controls that code, but they aren’t hooked up.



If you call any of these Dart methods now, they result in “Not Implemented” exceptions because you haven’t implemented the host side in the plugin. That’s the next step.

今、これらのDartメソッドを呼び出すと、プラグインでホスト側を実装していないため、「Not Implemented」例外(exception)が発生します。これが次のステップです。

Hooking things up on iOS

First, modify the plugin to create and start a synthesizer instance:



@implementation PluginCodelabPlugin {
  int _numKeysDown;
  FLRSynthRef _synth;


- (instancetype)init {
  self = [super init];
  if (self) {
    _synth = FLRSynthCreate();
  return self;

- (void)dealloc {

Next, start handling messages sent over the channel:



- (void)handleMethodCall:(FlutterMethodCall *)call
                  result:(FlutterResult)result {
  if ([@"getPlatformVersion" isEqualToString:call.method]) {
    result([@"iOS "
        stringByAppendingString:[[UIDevice currentDevice] systemVersion]]);
  } else if ([@"onKeyDown" isEqualToString:call.method]) {
    FLRSynthKeyDown(_synth, [call.arguments[0] intValue]);
    _numKeysDown += 1;
  } else if ([@"onKeyUp" isEqualToString:call.method]) {
    FLRSynthKeyUp(_synth, [call.arguments[0] intValue]);
    _numKeysDown -= 1;
  } else {

Notice that the code now looks for the onKeyDown and onKeyUp messages as well.



In order to get the key argument, pull it from call.arguments.

引数 key を取得するために、call.argumentsから引数を取得しています。


The returned value is boxed as an NSNumber (described in the Platform channels documentation), so convert it with intValue.

返された値はNSNumberとしてボックス化されているので(Platform channelsのドキュメントに記載)、intValueで変換(キャスト)しています。

See the completed file, PluginCodelabPlugin.m.

Hooking things up on Android

First, modify the plugin to create and start a synthesizer instance:



public class PluginCodelabPlugin implements FlutterPlugin, MethodCallHandler {
  private MethodChannel channel;
  private Synth synth;
  private static final String channelName = "plugin_codelab";

  private static void setup(PluginCodelabPlugin plugin, BinaryMessenger binaryMessenger) {
    plugin.channel = new MethodChannel(binaryMessenger, channelName);
    plugin.synth = new Synth();

  public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
    setup(this, flutterPluginBinding.getBinaryMessenger());

Next, start handling messages sent over the channel:


public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
  if (call.method.equals("getPlatformVersion")) {
    result.success("Android " + android.os.Build.VERSION.RELEASE);
  } else if (call.method.equals("onKeyDown")) {
    try {
      ArrayList arguments = (ArrayList) call.arguments;
      int numKeysDown = synth.keyDown((Integer) arguments.get(0));
    } catch (Exception ex) {
      result.error("1", ex.getMessage(), ex.getStackTrace());
  } else if (call.method.equals("onKeyUp")) {
    try {
      ArrayList arguments = (ArrayList) call.arguments;
      int numKeysDown = synth.keyUp((Integer) arguments.get(0));
    } catch (Exception ex) {
      result.error("1", ex.getMessage(), ex.getStackTrace());
  } else {

Similar to iOS, the code now looks for the onKeyDown and onKeyUp messages. Use arguments.get() to extract the key value here, as well. Make sure that, on Android, your plugin handles any exceptions that might arise.

iOSと同様に、onKeyDown と onKeyUpメッセージを探しています。


See the completed file, PluginCodelabPlugin.java.

8. Implement the example UI

Now that the plugin implements all of the plumbing, you probably want to see it in action. For that, you implement a simple keyboard UI example app:



import 'package:flutter/material.dart';
import 'dart:async';

import 'package:flutter/services.dart';
import 'package:plugin_codelab/plugin_codelab.dart';

enum _KeyType { Black, White }

void main() {
      .then((_) {
    runApp(new MyApp());

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

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

  void initState() {

  // Platform messages are asynchronous, so we initialize in an async method.
  Future<void> initPlatformState() async {
    String? platformVersion;
    try {
      platformVersion = await PluginCodelab.platformVersion;
    } on PlatformException {
      platformVersion = 'Failed to get platform version.';

    if (!mounted) return;

    setState(() {
      _platformVersion = platformVersion;

  void _onKeyDown(int key) {
    print("key down:$key");
    PluginCodelab.onKeyDown(key).then((value) => print(value));

  void _onKeyUp(int key) {
    print("key up:$key");
    PluginCodelab.onKeyUp(key).then((value) => print(value));

  Widget _makeKey({@required _KeyType keyType, @required int key}) {
    return AnimatedContainer(
      height: 200,
      width: 44,
      duration: Duration(seconds: 2),
      curve: Curves.easeIn,
      child: Material(
        color: keyType == _KeyType.White
            ? Colors.white
            : Color.fromARGB(255, 60, 60, 80),
        child: InkWell(
          onTap: () => _onKeyUp(key),
          onTapDown: (details) => _onKeyDown(key),
          onTapCancel: () => _onKeyUp(key),

  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        backgroundColor: Color.fromARGB(255, 250, 30, 0),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: <Widget>[
              Text('Running on: $_platformVersion\n'),
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: <Widget>[
                  _makeKey(keyType: _KeyType.White, key: 60),
                  _makeKey(keyType: _KeyType.Black, key: 61),
                  _makeKey(keyType: _KeyType.White, key: 62),
                  _makeKey(keyType: _KeyType.Black, key: 63),
                  _makeKey(keyType: _KeyType.White, key: 64),
                  _makeKey(keyType: _KeyType.White, key: 65),
                  _makeKey(keyType: _KeyType.Black, key: 66),
                  _makeKey(keyType: _KeyType.White, key: 67),
                  _makeKey(keyType: _KeyType.Black, key: 68),
                  _makeKey(keyType: _KeyType.White, key: 69),
                  _makeKey(keyType: _KeyType.Black, key: 70),
                  _makeKey(keyType: _KeyType.White, key: 71),

Notice the following:



  • You must import 'package:plugin_codelab/plugin_codelab.dart' in order to use the plugin. The dependency of the example on the plugin is defined in example/pubspec.yaml, which makes this work.

このプラグインを使用するには、’package:plugin_codelab/plugin_codelab.dart’ をインポートする必要があります。プラグインに対するサンプルの依存関係は、example/pubspec.yaml で定義されているので、これで動作します。


  • In main(), the orientation is forced to be landscape so that the whole keyboard can fit on the screen.



  • The _onKeyDown() and _onKeyUp() methods are both clients of the plugin API designed in previous steps.

_onKeyDown() と _onKeyUp()メソッドは両方とも前のステップで実装したプラグインAPIのクライアントです。


  • The code uses InkWell, which are just interactive rectangles, to draw the individual keys.



Run the app to see your functioning music keyboard:


cd example
flutter run

It should look like this:

9. Congratulations!



Next steps

  • Add end-to-end testing. The Flutter team provides a library to create end-to-end integration tests called e2e.



  • Publish to pub.dev. After you create a plugin, you may want to share it online so that others can use it. You can find the full documentation on publishing your plugin to pub.dev in Developing plugin packages.


Extend the synthesizer

For fun, if you want to play around with the synthesizer and improve it, here are some next steps you might consider:




  • Did you notice the popping sounds when you press and release a key? That’s due to the oscillator abruptly turning on and off. Usually synthesizers remedy that with amplitude envelopes.



  • Right now you can only play one key at a time. That’s called monophonic. Real pianos are polyphonic.