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があります。
- Dart Packages
- Flutterフレームワークのみを使ったPackageです。Dartだけで書きます。
- Plugin Packages
- iOSやAndroidのネイティブAPIを活用したPackageです。Dartだけではなく、iOSならSwiftかObjective-C、AndroidならKotlinかJavaを書くことになります。
ややこしいのですが、この記事は「後者の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
--org com.example
の部分はパッケージ名を表しています。--platforms=android
の部分は対応するプラットフォームを表しています。もしiOSも対応する場合は--platforms=android,ios
のようにカンマ区切りにします。-i swift
および-a kotlin
を付けなかった場合、iOSでObjective-C、AndroidでJavaを使うことになります。
Flutter側の実装
作成した雛型をAndroid Studioで開いてみます。
プロジェクトの構成は以下のようになっているかと思います。
このうち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の実装を確認することができます。
プラグインの開発中にデバッグするのに便利です。
(ここで古い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開発に取り組むきっかけや技術面での一助になれば幸いです。