【Android】ゼロから始めるFlipper Plugin

こんにちは、こんばんは、kitakkunです。

大学の卒業研究のテーマ上、Android端末とPCで通信を行うための手段が必要になり、Flipper Pluginを使おうということになりました。

業務でもFlipperは使うのですが、基本的なセットアップは完了している状態で、ゼロから始めようとしたときにどうしたら良いかわからず詰まりまくったのでメモ書きしておこうと思います。

# 前提事項

AndroidアプリケーションをFlipperでデバッグすることを想定しています。
iOS や ReactNative に関しては特に触れませんので予めご了承ください。

# Flipperとはなんぞ？

Flipperは、Meta社（旧Facebook）が開発している主にモバイルアプリケーション向けのデバッグツールになります。

https://fbflipper.com/

独自にプラグインを追加することもでき、機能を拡張し自分のプロジェクトのデバッグがやりやすいようにカスタマイズしていくことが可能です。

公式ドキュメントで[Flipperプラグインの作り方](https://fbflipper.com/docs/tutorial/intro/)が解説されていますので、そちらを読んでわかる方はそちらを読んでいただければと思います。当記事は公式ドキュメントを噛み砕きつつ躓きそうなポイントに補足を入れながらFlipperプラグインを開発していきます。

## Flipperのインストール

[公式ページ](https://fbflipper.com/)から落としてくるか、brewが使える方は以下のコマンドを叩いて入れましょう。
```shell
$ brew install --cask flipper
```

:::note warn
記事執筆時点の最新バージョン `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への依存追加
依存を追加します。
```kotlin
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はこの後作成します。

```kotlin
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()
    }
  }
}
```

:::note warn
`BuildConfig.DEBUG` は自分のパッケージのものを参照しているか十分に確認してください。
誤ってFlipperのライブラリに含まれるBuildConfigを参照したりすると、あれ？動かないぞ？となることがあります。
また、新規プロジェクトを作成した際、デフォルトでBuildConfigは生成されないはずなので、Gradleに次の設定が存在するか確認してください。
```kotlin
android {
    buildFeatures {
        buildConfig = true
    }
}
```
:::

AndroidManifest.xml にも Applicationクラスを忘れずに指定しておきます。

```xml
<application
    android:name=".MyApplication"
    ...
>...
```

## MyPlugin（独自のFlippper Plugin）の実装

まず、MyPluginの実装のベースを以下に示します。まだコネクションの保持と解消しかやっていないコードになります。

id はデスクトッププラグインの方も同じものを使いますので、覚えておいてください。

```kotlin
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`の中身を次のように書き換えます。

```kotlin
    override fun onConnect(connection: FlipperConnection?) {
        this.connection = connection
        this.connection?.send("connect", "\"接続されたよ！！\"")
    }
```

:::note warn
注意点としては、`FlipperConnection.send` で第2引数に指定して送信するデータはJSON形式でなければならないということです。今回は文字列単体を送信しますが、`\"` で囲んでやらないと正しいJSONにならずデスクトップクライアント側でパースエラーが発生します。
:::

Androidアプリで実装するのはここまでです。デスクトップ側の実装もやっていきましょう！

# デスクトップ側の実装

## プロジェクトの作成

事前に`npx`コマンドを使える状態にしておいてください。
次のコマンドを叩いてプロジェクトを作成します。

```shell
$ 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 の編集

デフォルトだとあれやこれやごちゃごちゃ書いてますが、次のように書き換えてみましょう。
```typescript
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` というのができるはずです。

1. Flipperを開く
2. 右上のMore → Add Plugins
3. Install Pluginsタブを選択
3. 左下の Select a Flipper package をクリック
3. 作成した tar を選択
3. 赤字でリフレッシュしろみたいなのが出るのでクリック

これでFlipperへのプラグインインストールが完了しました。

# いざ動作確認

アプリをエミュレータもしくはデバッグ接続中の端末で起動して次の表示になったらうまくいっています。ちなみに debuggable なビルドタイプでないと動かないのでご注意を。

![Screenshot 2023-10-21 at 1.46.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1971624/9a24b8d6-570f-d0e9-ea1c-b3c3b68fd40e.png)

# サンプルリポジトリ

サンプルのリポジトリをGitHubに公開したのでよろしければご覧ください。

https://github.com/kitakkun/flipper-android-sample

# 参考文献
- https://fbflipper.com/docs/getting-started/android-native/
- https://fbflipper.com/docs/tutorial/android/
- https://fbflipper.com/docs/tutorial/js-setup/