RACCOON TECH BLOG

株式会社ラクーンホールディングスのエンジニア/デザイナーから技術情報をはじめ、世の中のためになることや社内のことなどを発信してます。

FlutterでPluginを自作してみた

こんにちは、技術戦略部開発第1チームのかしざきです。

普段はWebの開発業務に携わっていますが、最近、プライベートでFlutterを使ったAndroidアプリ開発にチャレンジしています。
Flutterでは、クロスプラットフォームなマテリアルデザインのWidgetやIcon等が標準で用意されており、コードベースで簡単にUIを作ることができます。また、Flutterで採用されているDartという言語は、JavaとJavaScriptの中間のような印象でWeb開発者にも取っつきやすいように感じます。

この記事では、Flutterで開発を進める過程でPluginを作る機会があったので、Pluginを自作する手順とその使い方について紹介したいと思います。
記事執筆時の開発環境は下記の通りです(flutter doctorの結果を一部抜粋)。

[✓] Flutter (Channel stable, 2.0.3, on macOS 11.2.3 20D91 darwin-x64, locale
    en-JP)
[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.2)
[✓] Android Studio (version 4.1)

そもそもFlutterのPluginとは?

Flutterには、Dart PackagesとPlugin Packagesの2種類のPackageがあります。

ややこしいのですが、この記事は「後者のPlugin Packagesを自作してみたよ」という話になります。

なぜPluginを作ろうと思ったか

Flutterで開発していると、プラットフォームのネイティブAPIを使いたくなることがよくあります。
そんな時、Pub.devで世界中の開発者が作ったPluginを検索することができます。丁度良いPluginが見つかれば、世界のどこかの開発者に感謝します。
もし丁度良いPackageが見つからなかったとしても、FlutterのMethodChannel APIを使えばプラットフォーム毎に必要な機能を自分で実装することができます。

それでも私がわざわざPluginを作ったのは、Pub.devで目的に合致したPluginを見つけられず、かつバックグラウンド処理の中でネイティブAPIを呼び出したかったからです。

FlutterでAndroidのバックグラウンド処理を実装するには、公式が提供しているandroid_alarm_managerというPluginを使うのが一般的なようです。
android_alarm_managerを使えば、任意のタイミングで実行するバックグラウンド処理を実装することができるのですが、その中でMethodChannel APIを使ってAndroidのネイティブAPIを呼び出そうとすると失敗してしまいます。
調査したところ、android_alarm_managerから作られるIsolateは、アプリ側のActivityではなくandroid_alarm_managerプラグイン経由で定義されるServiceに紐づくものであるため、MethodChannel API経由で呼び出す独自のハンドラーを定義できないと分かりました。

そこで解決策となるのがPluginを自作してしまうことです。
android_alarm_managerで実行するタスクの中で自作のPluginを呼び出せば良いのです。

Pluginの作り方

前置きが長くなりましたが、ここからFlutterのPlugin Packagesを作る手順について記載していきます。
基本的にはFlutterの公式ドキュメントに沿って進めていきます。

Plugin用のプロジェクトを作る

まずは下記のようなコマンドでPluginの雛型を作成します。
今回はKotlinでAndroid用のPluginだけを開発したかったので下記のコマンドを利用しました。

flutter create --org com.example --template=plugin --platforms=android -a kotlin plugin_example

Flutter側の実装

作成した雛型をAndroid Studioで開いてみます。
プロジェクトの構成は以下のようになっているかと思います。
Flutter Package
このうちFlutter側の実装を行うのは、plugin_example/lib/plugin_example.dartです。

import 'dart:async';

import 'package:flutter/services.dart';

class PluginExample {
  static const MethodChannel _channel =
      const MethodChannel('plugin_example');
  static Future<String> get platformVersion async {
    final String version = await _channel.invokeMethod('getPlatformVersion');
    return version;
  }
}

非常にシンプルですね。
MethodChannel APIを使ったことがあれば簡単に理解できる内容ですが、Flutter側からAndroid側のgetPlatformVersionというメソッドを呼び出して端末のバージョンを取得しようとしています。

Android側の実装

次にAndroid側の実装を見てみます。
src/main/kotlin/com/example/plugin_example/PluginExamplePlugin.ktこのファイルにAndroid側の実装があります。

package com.example.plugin_example
import androidx.annotation.NonNull
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.PluginRegistry.Registrar
/** PluginExamplePlugin */
class PluginExamplePlugin: FlutterPlugin, MethodCallHandler {
  /// The MethodChannel that will the communication between Flutter and native Android
  ///
  /// This local reference serves to register the plugin with the Flutter Engine and unregister it
  /// when the Flutter Engine is detached from the Activity
  private lateinit var channel : MethodChannel
  override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
    channel = MethodChannel(flutterPluginBinding.binaryMessenger, "plugin_example")
    channel.setMethodCallHandler(this)
  }
  override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
    if (call.method == "getPlatformVersion") {
      result.success("Android ${android.os.Build.VERSION.RELEASE}")
    } else {
      result.notImplemented()
    }
  }
  override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
    channel.setMethodCallHandler(null)
  }
}

Flutter側よりは少し長いですが、そんなに難しい内容ではありません。
Flutter側のMethodChannel.invokeMethodにより、Android側のonMethodCallが発火します。
今回はgetPlatformVersionという名前で呼ばれてresult.success(Android ${android.os.Build.VERSION.RELEASE})というようにresult.successの引数に文字列を渡す形で結果をFlutter側に返しています。

シンプルな例ですが、この要領でAndroidのネイティブAPIの様々な機能をFlutterから呼び出すことができます。
紙幅の都合上ソースは割愛しますが、私の場合はAndroidのUsageStatsManagerを活用し、指定期間におけるアプリの利用時間や利用回数を取得するPluginを作成することができました。

exampleの実装

Pluginのテンプレートの中には、さらにFlutterのプロジェクトが内包されています。それがexampleというプロジェクトです。
plugin_example/example/lib/main.dartでRunすると、Pluginの実装を確認することができます。
プラグインの開発中にデバッグするのに便利です。

debug on Android
(ここで古いAndroidを使っていることがばれます)
この部分の実装は、Pluginを公開する場合(後述)、Pub.devのExampleのところに掲載される内容になります。

Pluginの使い方

Pluginを公開する

本記事の前半で触れた通り、作成したPluginはPub.devに公開することができます。
一旦--dry-runオプションで問題がないか確認したうえで

flutter pub publish --dry-run

公開できます。

flutter pub publish

Pluginの公開については別途ドキュメントがありますので詳しくはこちらをご参照ください。
公開されたPluginは誰もが気軽に活用することができますが、一度公開してしまうと削除できないので、その点は注意が必要です。

Pluginを公開したくない場合

諸々の事情で自作したPluginを公開せずに使いたい場合もあるかと思います。
この場合、自作したPluginのソースをアプリ本体に含めて参照する方法とGithubに公開して参照する方法を取ることができます。

Pluginのソースをアプリ本体に含めて参照する方法

自作したPluginをアプリ本体のプロジェクトに含め、そのPluginを利用したいアプリのpubspec.yamlを下記のように編集します。

dependencies:
  plugin_example:
    path: ../plugin_example/

相対パスでも絶対パスでも利用できますが、相対パスで記載する場合はpubspec.yamlが基点となります。
アプリ本体とPluginが並列に並んでいれば上記のような書き方になります。
編集したらpub upgradeしましょう。これでpub.devに公開されたPluginと同じように利用することができます。

PluginのソースをGithubに公開して参照する方法

自作したPluginをGithubに公開し、そのPluginを利用したいアプリのpubspec.yamlを下記のように編集します。

dependencies:
  plugin_example:
    git:
      url: git@github.com:XXXXX/plugin_example.git
      ref: HEAD

編集したらpub upgradeしましょう。これでpub.devに公開されたPluginと同じように利用することができます。
git管理しているPlugin側を更新した場合も同コマンドで最新のものが取得できます。

まとめ

以上、FlutterでPluginを自作して活用する方法をご紹介しました。
意外と簡単に感じた方が多かったのではないでしょうか?
私はSwiftやKotlinでネイティブアプリ開発をしたことはありませんが、Webエンジニアでも開発しやすく、かつ実機ですぐに動かせるので、Flutterでの開発は楽しいと感じています。
この記事がFlutter開発に取り組むきっかけや技術面での一助になれば幸いです。

一緒にラクーンのサービスを作りませんか? 採用情報を詳しく見る

関連記事

運営会社:株式会社ラクーンホールディングス(c)2000 RACCOON HOLDINGS, Inc