こんにちは、こんばんは、kitakkunです。
大学の卒業研究のテーマ上、Android端末とPCで通信を行うための手段が必要になり、Flipper Pluginを使おうということになりました。
業務でもFlipperは使うのですが、基本的なセットアップは完了している状態で、ゼロから始めようとしたときにどうしたら良いかわからず詰まりまくったのでメモ書きしておこうと思います。
前提事項
AndroidアプリケーションをFlipperでデバッグすることを想定しています。
iOS や ReactNative に関しては特に触れませんので予めご了承ください。
Flipperとはなんぞ?
Flipperは、Meta社(旧Facebook)が開発している主にモバイルアプリケーション向けのデバッグツールになります。
独自にプラグインを追加することもでき、機能を拡張し自分のプロジェクトのデバッグがやりやすいようにカスタマイズしていくことが可能です。
公式ドキュメントでFlipperプラグインの作り方が解説されていますので、そちらを読んでわかる方はそちらを読んでいただければと思います。当記事は公式ドキュメントを噛み砕きつつ躓きそうなポイントに補足を入れながらFlipperプラグインを開発していきます。
Flipperのインストール
公式ページから落としてくるか、brewが使える方は以下のコマンドを叩いて入れましょう。
$ brew install --cask flipper
記事執筆時点の最新バージョン 0.229.0
に関してはどうやらバグってるらしいです
→https://github.com/facebook/flipper/issues/5228
そのため、brewで入れず https://github.com/facebook/flipper/releases/tag/v0.228.0 経由で古いバージョン 0.228.0
を入れることをお勧めします。
Flipper Pluginの構造
Flipper Pluginはモバイルアプリケーション側の実装とデスクトップアプリケーション側の実装に分かれています。
先にモバイルアプリケーション側の実装を行った後、デスクトップアプリケーションの実装を行います。
Android側の実装
Gradleへの依存追加
依存を追加します。
repositories {
mavenCentral()
}
dependencies {
val flipperVersion = "0.228.0"
debugImplementation("com.facebook.flipper:flipper:$flipperVersion")
debugImplementation("com.facebook.soloader:soloader:0.10.5")
releaseImplementation("com.facebook.flipper:flipper-noop:$flipperVersion")
}
Applicationクラスの作成
Applicationクラスが定義されていなければ Application クラスを作り、次のように記述します。
MyPluginはこの後作成します。
class MyApplication : Application {
override fun onCreate() {
super.onCreate()
SoLoader.init(this, false)
if (BuildConfig.DEBUG && FlipperUtils.shouldEnableFlipper(this)) {
val client = AndroidFlipperClient.getInstance(this)
client.addPlugin(MyPlugin())
client.start()
}
}
}
BuildConfig.DEBUG
は自分のパッケージのものを参照しているか十分に確認してください。
誤ってFlipperのライブラリに含まれるBuildConfigを参照したりすると、あれ?動かないぞ?となることがあります。
また、新規プロジェクトを作成した際、デフォルトでBuildConfigは生成されないはずなので、Gradleに次の設定が存在するか確認してください。
android {
buildFeatures {
buildConfig = true
}
}
AndroidManifest.xml にも Applicationクラスを忘れずに指定しておきます。
<application
android:name=".MyApplication"
...
>...
MyPlugin(独自のFlippper Plugin)の実装
まず、MyPluginの実装のベースを以下に示します。まだコネクションの保持と解消しかやっていないコードになります。
id はデスクトッププラグインの方も同じものを使いますので、覚えておいてください。
class MyPlugin : FlipperPlugin {
private var connection: FlipperConnection? = null
override fun getId() = "my-plugin"
override fun onConnect(connection: FlipperConnection?) {
this.connection = connection
}
override fun onDisconnect() {
connection = null
}
override fun runInBackground() = false
}
connection
を介して、簡単なメッセージを送信してみることにします。今回は接続が成功した際に、デスクトップのFlipperに "接続されたよ!!"
と表示してみることにしましょう。
onConnect
の中身を次のように書き換えます。
override fun onConnect(connection: FlipperConnection?) {
this.connection = connection
this.connection?.send("connect", "\"接続されたよ!!\"")
}
注意点としては、FlipperConnection.send
で第2引数に指定して送信するデータはJSON形式でなければならないということです。今回は文字列単体を送信しますが、\"
で囲んでやらないと正しいJSONにならずデスクトップクライアント側でパースエラーが発生します。
Androidアプリで実装するのはここまでです。デスクトップ側の実装もやっていきましょう!
デスクトップ側の実装
プロジェクトの作成
事前にnpx
コマンドを使える状態にしておいてください。
次のコマンドを叩いてプロジェクトを作成します。
$ npx flipper-pkg init
色々聞かれますが、ここでは次のように入力していきます。
質問 | 雑な和訳 | 入力するもの |
---|---|---|
You are about to create a plugin in a directory that isn't watched by Flipper. Should we add [現在のパス] to the Flipper search path?(Ctrl^C to abort) | 訳:今作業しているディレクトリがFlipperのプラグイン監視対象から外れてるから、追加しておく? | Y |
Plugin Type ("client" if the plugin will work with a mobile app, "device" if the plugin will work with a mobile device) | プラグインタイプはどうする?client?device? | client |
ID (must match native plugin ID, e.g. returned by getId() in Android plugin) | プラグインのIDを決めてね。Androidで作ったFlipperPluginクラスのgetId() の戻り値と同じだよ |
my-plugin |
Title (will be shown in the Flipper main sidebar) | Flipperのデスクトップアプリでプラグインを表示するときのタイトル | my-plugin(※なんでもいい) |
yarn install
が動いて色々入ります。気長に待ちます。
src/index.tsx の編集
デフォルトだとあれやこれやごちゃごちゃ書いてますが、次のように書き換えてみましょう。
import React from 'react';
import {PluginClient, usePlugin, createState, useValue} from 'flipper-plugin';
type Events = {
connect: string;
};
export function plugin(client: PluginClient<Events, {}>) {
const messageState = createState<string>("", {persist: 'data'});
client.onMessage('connect', (message) => {
messageState.set(message);
});
return {messageState};
}
export function Component() {
const instance = usePlugin(plugin);
const message = useValue(instance.messageState);
return (
<>
{message}
</>
);
}
clientからの connect イベントを待ち受けて、それを messageState に保持、表示するといったものになります。
tarファイルを作ってFlipperに突っ込む
リアルタイムでデバッグしながら開発する方法もあるっぽいのですがこれが一番気軽なのでこれを紹介します。
開発中のデスクトップ用Flipper Pluginのプロジェクトのルートで次のコマンドを叩きましょう。
$ npx flipper-pkg pack
するとプラグインが入ったtarファイルができます。今回は flipper-plugin-my-plugin-1.0.0.tgz
というのができるはずです。
- Flipperを開く
- 右上のMore → Add Plugins
- Install Pluginsタブを選択
- 左下の Select a Flipper package をクリック
- 作成した tar を選択
- 赤字でリフレッシュしろみたいなのが出るのでクリック
これでFlipperへのプラグインインストールが完了しました。
いざ動作確認
アプリをエミュレータもしくはデバッグ接続中の端末で起動して次の表示になったらうまくいっています。ちなみに debuggable なビルドタイプでないと動かないのでご注意を。
サンプルリポジトリ
サンプルのリポジトリをGitHubに公開したのでよろしければご覧ください。