Mirrativ tech blog

株式会社ミラティブの開発者(バックエンド,iOS,Android,Unity,機械学習,インフラ, etc...)によるブログです

【Android】FlipperのCustomPluginを作成してデバッグ効率を改善する

Mirrativ Androidエンジニアのmorizoooです。MirrativではデバッグツールとしてFlipperを使っています。Flipperはモバイルアプリデバッグのためのデスクトップアプリケーションで、アプリ内のデータの整形や可視化を行うことができます。また、Flipperはネットワークの通信状況を確認するNetworkPluginなど、標準でいくつかの機能が用意されています。詳細についてはこちらをご覧ください。 tech.mirrativ.stream

Flipperは標準機能だけでなく、独自のCustomPluginを作成することもできます。MirrativではCustomPluginを積極的に作成し、バグ調査や開発効率の改善に役立てています。一つ例を上げると、Mirrativではコメントやギフトの機能のために、WebSocketベースの独自のPubSubライブラリを使用しています。以前はここでなにか問題が起こったときにペイロードのJSONをLogcatに出力してデバッグしていました。現在ではFlipperのCustomPluginを利用しており、これによりデータの検索、フィルター、閲覧がしやすくなり、バグの原因を特定するスピードが圧倒的に改善しました。

今回の記事では、実装例としてクライアント一覧でJSONを表示するCustomPluginの作成方法についてお話します。

はじめに

FlipperはElectron+Reactで作られており柔軟なカスタマイズが可能です。

Building A Custom Desktop Plugin | Flipper

単純なデータを表示する場合はCreate Table Pluginがテーブル表示、検索がついているので便利です。

Create Table Plugin | Flipper

Create Table Pluginを用いて日時とJSONがテーブルで表示され、選択するとサイドバーにJSONの詳細が表示されるプラグインを作成します。 f:id:morizo999:20210222130130p:plain:w600

Pluginの開発の流れは以下になります。

  1. FlipperのDesktopPluginを作成
  2. Androidで作成したDesktopPluginと接続するためのClientを作成
  3. AndroidはClientからデータを流す
  4. FlipperのDesktopPluginでデータが表示されるか確認

まずはAndroidのデータを受けるためのFlipperのDesktopPluginの作成をします。

Desktop Pluginsの作成

Plugin structure | Flipper

flipper-pkgを使用して雛形を作成します。

$ npx flipper-pkg init
# ~/.flipper/config.jsonにpluginのpathが追加される
? You are about to create a plugin in a directory that isn't watched by Flipper. Should we add /github.com/
morizooo/flipper-plugin-table-example to the Flipper search path? (Ctrl^C to abort) Yes
⚙️  Added '/Users/morizooo/dev/src/github.com/morizooo/flipper-plugin-table-example' to the search paths in '/Users/morizooo/.flipper/config.json'
# AndroidのFlipperPluginとFlipperのDesktopPluginをつなぐためのID
? ID (must match native plugin ID, e.g. returned by getId() in Android plugin): json-viewer
# Desktopアプリで表示される名前を設定
? Title (will be shown in the Flipper main sidebar): JsonViewer
⚙️  Initializing Flipper desktop template in /Users/morizooo/dev/src/github.com/morizooo/flipper-plugin-table-example/flipper-plugin-table

Create Table Pluginを使いたいので、package.jsonからflipper-pluginの依存をなくし、

$ npm install

を実行します。
※ flipper-plugin を使うとindex.tsxにComponentとpluginの実装が求められ、createTablePluginを利用することができません。そのため flipper-plugin を依存からはずし代わりに flipper のcreateTablePluginをimportし利用しています。

The following mechanism isn't supported yet by the Sandy plugin architecture. Please remove flipper-plugin from the plugin dependencies if you want to use createTablePlugin.

index.tsxを以下の内容に編集します。

import {Panel, ManagedDataInspector, TableBodyRow, createTablePlugin} from 'flipper';

# idは必須
type Row = {
  id: string
  date: string
  json: string
};

type Events = {
  newRow: Row
};

const columns = {
  date: {
    value: 'date',
  },
  json: {
    value: 'json',
  },
};

# 表示領域の設定
const columnSizes = {
  date: '15%',
  json: 'flex',
};

# 表をタップした時にサイドバーに表示される内容を設定
function renderSidebar(row: Row) {
  return (
    <Panel floating={false} heading={'Info'}>
      <ManagedDataInspector data={JSON.parse(row.json)} expandRoot={true}/>
    </Panel>
  );
}

# 表のデータを設定
function buildRow(row: Row): TableBodyRow {
  return {
    columns: {
      date: {
        value: row.date,
        filterValue: row.date,
      },
      json: {
        value: row.json,
        filterValue: row.json,
      },
    },
    key: row.id,
    copyText: JSON.stringify(row),
    filterValue: `${row.date} ${row.json}`,
  };
}

export default createTablePlugin<Row>({
  method: 'newRow',
  columns,
  columnSizes,
  renderSidebar,
  buildRow,
});

編集後に以下のコマンドを実行しpluginのビルドをします。

$ npm run watch

Android Pluginsの作成

次はFlipperのDesktopPluginにデータを送信するためのAndroidのClientを作成します。FlipperPluginを継承してJsonViewerFlipperPluginを作成します。 getIdのMethodにはnpx flipper-pkg initで指定した値を設定します。

class JsonViewerFlipperPlugin : FlipperPlugin {
    private var connection: FlipperConnection? = null
    private var id = 1

    // npx flipper-pkg initで指定したID
    override fun getId(): String = "json-viewer"

    override fun onConnect(connection: FlipperConnection?) {
        this.connection = connection
    }

    override fun onDisconnect() {
        connection = null
    }

    // flipperを開いていなくてもデータが溜まるようにする
    override fun runInBackground(): Boolean = true

    fun insert(json: String) {
        connection?.send("newRow",
            FlipperObject.Builder()
                .put("id", id) // idは必須。一意の値にする
                .put("date", SimpleDateFormat("HH:mm:ss.SSS", Locale.JAPAN).format(Date()))
                .put("json", json)
                .build())

        id += 1
    }
}

Pluginを作成したらFlipperClientに登録します

class DebugApplication : MyApplication() {
    override fun onCreate() {
        SoLoader.init(applicationContext, false)
        if (BuildConfig.DEBUG && FlipperUtils.shouldEnableFlipper(applicationContext)) {
            val client = AndroidFlipperClient.getInstance(applicationContext).apply {
                addPlugin(InspectorFlipperPlugin(applicationContext, DescriptorMapping.withDefaults()))
                addPlugin(JsonViewerFlipperPlugin())
            }
            client.start()
        }
        super.onCreate()
    }
}

MainActivityから呼び出します

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.insertButton.setOnClickListener {
            FlipperManager.tableFlipperPlugin.insert(JSON)
        }
    }

    companion object {
        // https://www.json-generator.com/
        const val JSON = "[\n" +
                "  {\n" +
                "    \"_id\": \"603268fa74f8cb0028c9283f\",\n" +
                "    \"index\": 0,\n" +
                "    \"guid\": \"80c4a122-81a3-4ff6-a871-969e5b87bb4b\",\n" +
                // 省略
                "  }\n" +
                "]"
    }
}

f:id:morizo999:20210222125536p:plain:w300

最終的な表示までのデータの詳細な流れは以下になります。

  1. [Android] Application のonCreateでFlipperをセットアップし、作成したPluginを登録
  2. [Android] MainActivity で作成したPluginのinsertをcallしてデータを流す
  3. [Android] 作成PluginはDesktopPluginとの connection を利用してデータをDesktopPluginへ送信(keyはnewRow、dataはJSON)
  4. [Desktop Plugin] DesktopPluginの createTablePlugin はnewRowを受けてJSONのデータを挿入&表示

ConsoleからFlipperを起動するとDisabledに追加されるのでEnabledにします。
※ 注意:開発中のプラグインはConsoleから起動しないと反映されません。一覧に存在しない場合は~/.flipper/config.json にPluginのpathが追加されているか確認してください

$ /Applications/Flipper.app/Contents/MacOS/Flipper

f:id:morizo999:20210222125626p:plain

Pluginの配布

Pluginの開発者ではない人がFlipperのpathを通したり、consoleから起動する必要がないようにPluginとしてtgzファイルをExportします。

Plugin Distribution | Flipper

npx flipper-pkg pack を実行するとtgzファイルができます。

View -> Manager Plugins...を選択しInstall Pluginsでtgzファイルを指定すれば追加できます。

f:id:morizo999:20210221234917p:plain:w600

おわりに

Create Table Pluginを使ったFlipperのCustomPluginの作り方について紹介しました。CustomPluginを導入するまでは分析ログの実装確認やバグが起きた時にServer or Clientのどこに問題があるのか特定するまでに時間がかかっていましたが、原因を特定するまでのスピードが改善しました。また、AndroidエンジニアだけではなくServerエンジニアでも検証時に使える点が嬉しいです。サンプルのソースはこちらにあります。

github.com

参考

star-zero.medium.com