Flutter on Desktopでアプリを常駐させる

https://qiita.com/yoyo-misc/items/3c3dcf37f1e2ce988b5c

この記事の続きです。Flutter Webではウィンドウの常駐化が出来ません。そこで本記事ではWindowsOSにてDesktopビルドを行ってアプリを常駐するまでを一緒に動かしてみたいと思います。Linuxだと需要がなさそうなので。

# アプリの常駐とは？

本記事ではバックグラウンドワーカーでタスクを走らせるのではなく、ウィンドウを閉じてもアプリがタスクトレイに格納されて常駐する方法を実現してみます。Hidden at launchする方法はwindow_manager.dartのReadmeに書かれていますが、GUIをもたないアプリであればC++/C#（ネイティブ言語）で開発する方が自然だと思うので本記事では取り上げません。

## タスクトレイに格納するパッケージの追加

https://pub.dev/packages/system_tray

window_manager.dartではタスクトレイに格納する機能がないのでsystem_tray.dartを利用して実現させます。まずはpub addなどであらかじめインストールを行っておいてください。

## ウィンドウを閉じてもアプリを終了させないようにする
window_manager.dartのウィンドウマネージャーの設定を変更してウィンドウを閉じてもアプリを終了させないようにします。
``` :dart
windowManager.waitUntilReadyToShow(windowOptions, () async {
  await windowManager.setPreventClose(true); <- add here
  await windowManager.show();
  await windowManager.focus();
});
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3698535/0106ac78-101a-c307-a6d2-ed04715ec4fb.png)
await windowManager.setPreventClose(true); を追記していったんビルドし、ウィンドウをバツボタンで閉じても反応しないことを確認してください。

## initSystemTrayメソッドを作成

```
Future<void> initSystemTray() async {
... omit code ...
}
```
ReadmeのUsageに記述されているコードをコピペします。

:::note warn
おことわり
私の記事ではリファレンスを参照してまるごと利用する場合はコードの全文を掲載しません。元記事が編集されている場合はフェッチが大変なのと書き写すこと自体が無駄と感じるため、あらかじめご了承ください。
:::


## initSystemTrayを実行
```
@override
void initState() {
  super.initState();
  windowManager.addListener(this);
  initSystemTray(); <- add here
}
```
リファレンスのExampleに書かれている方法で実装します。アイコンはご自身で用意します。

### ☕寄り道
Future<void>の場合はawaitをつけなくていいの？ 👉ページの最後にて後述します。

## ウィンドウを閉じるボタンを押したらウィンドウを非表示にする 
``` :dart
@override
void onWindowClose() async {
  await windowManager.hide();
  setState(() {});
}
```

## タスクトレイの機能を変更する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3698535/2038a54c-2954-c174-24f8-3f27c364c0a1.png)
リファレンスのサンプルコードではShowとHideの切り替えがメニューに設定されていますが、現時点ではウィンドウを閉じるボタンを押すと非表示、タスクトレイのアイコンを左クリックするとウィンドウが表示される動作が確認できていると思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3698535/dc9575fc-c1bb-1320-03e0-2ec500d62baa.png)
表示・非表示メニューは不要なのでかわりにCountUpをメニューに追加してみます。コードは次に記します。

## メニューの動作とコードを揃える
```
MenuItemLabel(
    label: 'CountUp',
    onClicked: (menuItem) => setState(() {
          _counter++;
        })),
MenuItemLabel(
    label: 'Exit',
    onClicked: (menuItem) async => await windowManager.destroy()),

```
* CuntUpメニューをクリックするとカウンターを1回増やす
* Exitメニューをクリックするとアプリを終了する

setPreventClose(true)でアプリの終了がガードされているので、destroy()でウィンドウマネージャーを破棄することでアプリ終了となります。

# タスクトレイ常駐で何をしたいのか？
複数ウィンドウの切り替えなどをやってみたかったのですが、Flutterでは出来ないか、力業で作り込む必要がありそうです。とりあえず今回はウィンドウ非表示の状態でstateを変更することを試せました。コードに慣れることも重要ですが、何を作りたいのかを考えることで学習や開発のモチベーションにつながります。ぜひ一緒に想像力を働かせていきましょう。

# おまけ：非同期メソッドをFutureクラスでラップしなくていいの？

```
initSystemTray();
↓
Future(() async {
  await initSystemTray();
  ... hre some code ...
});

void main() async {}
↓
Future<void> main() async {}
```

Futureクラスでラップしなくても動作するので疑問に思っていたのですが、以下のスレッドを見つけたので共有します。

https://stackoverflow.com/questions/56244025/whats-the-difference-between-returning-void-vs-returning-futurevoid

会話言語の解釈はひとそれぞれだと思うので翻訳はしませんが、少なくともvoidとFure<void>の違いはawaitによって処理の完了を知る必要があるかどうかで使い分けましょうと説明されています。

# この記事の続き

https://qiita.com/yoyo-misc/items/25d069480abb63353224