フューチャー技術ブログ

FlutterをElectronと組み合わせる

TIGの伊藤真彦です。

最近はFlutterの研究を進めており、一人Flutter連載のような動きをしています。

FlutterをElectronと組み合わせる

  • 入門記事: Flutterであればデスクトップアプリケーションを構築できることを説明しました
  • Electronの入門記事: これも私が書きましたが、Electronもクロスプラットフォームのデスクトップアプリケーションを開発できるライブラリです

ElectronはHTML、JavaScriptをアセットとして利用してデスクトップアプリケーションとして動かすことができます、結論としてはFlutterアプリケーションをWeb向けにビルドして、その成果物をElectronアプリケーションとしてビルドできました。

なぜそのような事を行うかというポイントですが2点あります。

  • Flutter on Desktop未対応の機能を使いたい
  • Electron向けの資産を活かしたい

Flutter on Desktop未対応の機能を使いたい

Flutter on Desktopはまだまだリリースから間もないため、安心して利用できるか見極めながら開発していく必要があります。
またFlutter向けパッケージのいくつかはFlutter on Desktopに対応していないものもあります。

例えば先日の記事で技術検証したgoogle_maps_flutterを利用したアプリケーションをデスクトップ向けにビルドすると、執筆時点ではアラートが表示され正常に動作しません。

ビルドエラー

同じソースコードをWeb向けにビルドし、Electronに組み込むと問題なく動作します。

ElectronでMap表示

Flutter on Desktopのエコシステムが充実するまでの繋ぎとしてこのような手法をとることができます。

Electron向けの資産を活かしたい

ビジネス要件的にどうしても必要な、Electronに向け最適化されたJavaScript、TypeScript製モジュールがありました。これらの資産をDart向けに作り直す必要をなくす、という意味でFlutter on Electronという組み合わせが実現できないかな、という検証を行ったという背景もあります。

あまり頼りすぎるとFlutter on Desktopに本格移行する難易度が跳ね上がりますが、この組み合わせであれば既存の資産や豊富なnpmモジュールを活用できます。

Flutter on Desktopのおさらい

Flutterアプリケーションをデスクトップアプリケーションとして動かすことはとても簡単にできます。

起動時、ビルド時のターゲットを指定するだけです。

flutter create myapp
cd myapp
flutter run -d macos
Flutter on Desktopおさらい

設定でデスクトップ向けのビルドが有効化されていない場合はconfigコマンドで有効化します。

flutter config --enable-macos-desktop

既存のプロジェクトで有効化する場合はconfigで有効化した後にカレントディレクトリでcreateコマンドを実行すると、対象のプラットフォーム向けの設定ファイルが用意されます。

flutter create .

この辺りの手軽さはやはり素晴らしいと感じますね。

FlutterアプリケーションをElectronと組み合わせる

さて本題です。

やる事自体はFlutterアプリケーションは素直にFlutter on the Webとして開発し、ビルド成果物を組み込むElectronライブラリを用意する形です。作成したmyappフォルダと同じ階層にElectron部分を用意するフォルダを作成します、名前はnodejsフォルダにしておきます。

project
├ myapp
└ nodejs

nodejsフォルダで諸々準備をするとElectronアプリケーションが利用できるようになります。

  • 依存モジュールのインストール
  • package.jsonの編集
  • 必要なファイルの配置

依存モジュールのインストール

nodejsフォルダでnpm initコマンドを実行し、Electronを導入します。electron-builderが「Yarn is strongly recommended instead on npm」と強く訴えているので、Yarnを使って依存モジュールを導入します。

cd nodejs
npm init -y
yarn
yarn add electron --dev
yarn add electron-builder --dev

package.jsonの編集

インストールが完了したら必要なファイルやコマンドを整備します。まずはpackage.jsonに下記の内容を追加します。

package.json
"main": "src/background.js",
"scripts": {
"start": "cd ../myapp flutter run",
"electron:start": "bash ../build.sh && electron src/background.js",
"electron:build": "bash ../build.sh && electron-builder"
}

startコマンドはピュアにFlutterアプリとして動かしたい場合にいちいちフォルダを移動するのが面倒なのでオマケのようなノリで追加しています。

必要なファイルの配置

追加したコマンドはElectronアプリケーションを起動、またはビルドする前にFlutterのビルドコマンドを記載したシェルを叩く、という仕組みにしています。

build.shは下記のような内容です。

build.sh
#!/bin/sh
# flutterアプリをビルド
cd ../mymap
flutter build web
# ビルド成果物をコピー
cp -r ./build/web/ ../nodejs/src/
# electronで動かすためhtmlの内容を修正
sed -i -e 's/base href=\"\/\"/base href=\"\"/' ../nodejs/src/index.html

Flutter on the Webにはindex.htmlに記載されたbaseタグを参照してJavaScriptのモジュールが動く仕組みになっています。

index.html
<!--
If you are serving your web app in a path other than the root, change the
href value below to reflect the base path you are serving from.

The path provided below has to start and end with a slash "/" in order for
it to work correctly.

For more details:
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base

This is a placeholder for base href that will be replaced by the value of
the `--base-href` argument provided to `flutter build`.
-->
<base href="$FLUTTER_BASE_HREF">

このbaseタグはビルド時のオプションで変更できます。
Webアプリとしてビルドする時に、デプロイする先のドメインを柔軟に変更できるためのオプションです。

flutter build web --base-href "/myapp/"

デフォルト値は/になっています。Electronで利用する場合、baseタグの値は空文字が都合が良いのですが、オプションで空文字を指定するとエラーが起きてしまいます。

$ flutter build web --base-href ""
base-href should start and end with /

仕方がないので一旦オプション無しでビルドして、sedコマンドで編集しています。

index.htmlのテンプレートを変更してしまっても良いですが、Flutter側の変更は控えることでElectronとFlutterの関係性をなるべく疎結合なものに保ちたい意図があります。ともかくこれだけの変更でFlutter on the Web向けにビルドしたファイルがElectronのアセットファイルとして利用できます。

あとはpackage.jsonに追加したコマンドで指定している場所(nodejs/src)にbackground.jsを配置しておきます。中身はよくあるElectron向けの起動スクリプトです。個人的にはVue.js向けのElectronプラグインが生成してくれるファイルが一番気が利いていると感じているのですが、Mac OSでのエッジケース向けの挙動などはそこから拝借しています。

background.js
const electron = require('electron');
const app = electron.app;
const BrowserWindow = electron.BrowserWindow;

const isDevelopment = process.env.NODE_ENV !== 'production'

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win

async function createWindow() {
// Create the browser window.
win = new BrowserWindow({
autoHideMenuBar: true,
useContentSize: true,
width: 1280,
height: 800,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
},
})
win.loadURL('file://' + __dirname + '/index.html');
}

// Quit when all windows are closed.
app.on('window-all-closed', () => {
app.quit()
})

app.on('activate', () => {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', async () => {
createWindow()
})

// Exit cleanly on request from parent process in development mode.
if (isDevelopment) {
if (process.platform === 'win32') {
process.on('message', (data) => {
if (data === 'graceful-exit') {
app.quit()
}
})
} else {
process.on('SIGTERM', () => {
app.quit()
})
}
}

パスはElectronをVue.jsと組み合わせた場合一般的にここになる、という意図でsrcに配置しています、名前がmain.jsになってもpackage.jsonに記載した内容と齟齬がなければ問題なく動きます。

Flutter on the Web向けのファイルをビルドするとmain.dart.jsというファイルが生成されますが、今後アップデートによる挙動の変化があってもFlutter側の成果物と名称がぶつからない名前にしておくと良いでしょう。background.jsという名称もVue.js向けプラグインが生成するファイルに倣っています。

上記の準備を終えるとElectronアプリケーションとしてFlutterのデモアプリが動きます。

cd nodejs
yarn electron:start
Flutter on Electron

Flutter on Desktopとして起動したものと比較すると、微妙にフォントが変わるなどの違いが発生しますが、どちらも快適に動作します(右がFlutter on Desktopです)。ウィンドウのリサイズ時の挙動などはFlutter on Desktopの方がスムーズです、この辺りは仕組み上仕方がないかな、といった印象です。

Flutter on Desktopとの表示の違い

今回の仕組みではFlutterアプリケーション自体は素直にFlutterアプリケーションとして開発できているので、Flutter on Desktopでも問題なければフットワーク軽めに移行できます。

デスクトップ対応は魅力的だけど、欲しい機能がまだ動かなかったので見送る、というパターンの時の選択肢としてはオススメできるかなと思います。

まとめ

  • Flutter on Desktop未対応のパッケージはまだ存在する
  • Flutter on the WebとElectronの組み合わせは簡単に実現できる

なかなかトリッキーな試みでブログ記事にするか迷いましたが、面白いという意見をいただけたのと意外と需要があるかも…?ということで記事にしてみました。