flutter（go-flutter）の知見まとめ

# はじめに

go-flutter を使う機会があったので、その際の知見や、詰まったポイントなどを残しておきます。
go-flutter に限らず、flutter の知見もあります。


# Row や Column の中にある TextField のよくあるエラー

何回遭遇したかわからない以下のエラー

```
The following assertion was thrown during performLayout():
```

Row や Column の children として、TextFiled を配置すると、ほぼ必ず起こります。解決策は、検索するとすぐ出てきますが、

```dart
Row(
  children: <Widget>[
    Flexible(
      child: TextField(
        (略)
      )
    )
  ]
)
```

こんな感じで、TextField を `Flexible` で囲ってあげると解決します。


# go-flutter: no handler found for channel ...

これは、go-flutter特有のエラーですね。自分の場合は、[url_launcher](https://pub.dev/packages/url_launcher) という、ブラウザに飛べるリンクを貼りたい時に使うパッケージを使ったときに起きました。このパッケージをインストールして、Usage をコピペして実行しても、うまくいきません。以下のエラーが出ます。

```
go-flutter: no handler found for channel plugins.flutter.io/url_launcher
```

なぜこういうエラーが出るかというと、多くのパッケージはモバイル（Androidとios）で動く用に作られているからです。flutterはそもそもがモバイル向けですから、まぁ当たり前です。「じゃあ、モバイルで使えた有用なパッケージは使えないのか」と思うかも知れませんが、go-flutter は `よく使うパッケージがプラグインという形で既に実装されている` んですね。最初から、コレを使えってことです。
プラグインの情報は[こちら](https://github.com/go-flutter-desktop/go-flutter/wiki/Plugin-info)、
そして、実装済のプラグイン一覧は以下です。[こちら](https://github.com/go-flutter-desktop/plugins)からも確認できます。

- image_picker
- path_provider
- package_info
- shared_preferences
- url_launcher
- video_player
- file_picker
- sqlite
- title_bar
- clipboard_manager
- open_file

実装済みプラグインの使い方は、上の一覧のリンク先から、更に使いたいプラグインのリンクに飛ぶと、記載されています。今回は、url_launcher を例に説明します。
`go/cmd/options.go` に使いたいプラグインを書き加えます。


```go:go/cmd/options.go
package main

import (
  "github.com/go-flutter-desktop/go-flutter"               // 最初から書いてある
  "github.com/go-flutter-desktop/plugins/url_launcher"     // url_launcher をインポート
)

var options = []flutter.Option{
	flutter.WindowInitialDimensions(800, 1280),            // 最初から書いてある
	flutter.AddPlugin(&url_launcher.UrlLauncherPlugin{}),  // url_launcher をプラグインとして追加
}
```

いつも通り `pubspec.yaml` も書き換えて、get します。

```yaml:pubspec.yaml
...
dependencies:
  url_launcher: ^5.4.2
...
``` 

```
$ pub get もしくは、 flutter pub get
```

そして、使いたいdartファイルでインポート

```dart:使いたいdartファイル
import 'package:url_launcher/url_launcher.dart';
```

この状態で `hover run` して上げれば、 このエラーを解決することができます。

# Pluginを自分で実装する

go-flutter を採用した時点で、goに何かしらの思いを馳せているはずです。go-flutterはそんな人の期待に答えます。めちゃくちゃざっくり説明すると、プラグインを自作することで、FFIなどを用いることなく、goの関数などをflutter(dart)から簡単に呼び出すことができます。ただ、筆者は上司が実装してくれたPluginを使う側だったので、詳しくわかっておりません。
気になる方は、[こちら](https://github.com/go-flutter-desktop/go-flutter/wiki/Implement-a-plugin)を参考にしてみてください。

# バイナリの実行

バイナリを実行したいと思ったときに、一番最初に目に留まるのが `Process class` にある `Process.run()` メソッドですね。
詳細は[こちら](https://api.flutter.dev/flutter/dart-io/Process-class.html)です。

これ、`ls` とか `pwd` とかは問題なく実行できるんですが、自作のバイナリを実行しようとしても、できません。

```
[ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: ProcessException: No such file or directory
```

こんなエラーがでます。ファイルが確実に存在していても、`No such file or directory` と出ます。もしかしたら、`Permission denied` というエラーが出るかもしれません。

これの対処法ですが、[process_run](https://pub.dev/packages/process_run) というパッケージを利用することです。最近出たパッケージですが、これには筆者らも相当助けられました。感謝しかありません。

使い方は、いつも通りです。公式ページのreadmeにも書いてあります。

```yaml:pubspec.yaml
...
dependencies:
  process_run: ^0.10.7
...
```

```
$ pub get もしくは、 flutter pub get
```

```dart:使いたいdartファイル
import 'package:process_run/process_run.dart';

(略)

// バイナリを実行
await run('./binary', [ '--version' ], verbose: true);   //　第2引数の配列は、引数です。
```

とても便利なので、同じことでお困りの方がいたら、ぜひ使ってみてください。


# 使用済みインスタンスに関するエラー

```
The createState function for **** returned an old or invalid state instance

flutter: Failed assertion: line **** pos *: '_state._widget == null'
```

例えば、以下のような StatefulWidget は、エラーを起こす危険性があります。

```dart:test.dart
class Test extends StatefulWidget {
  TestState _testState = TestState();

  bool isCheck() {
    return _testState.isCheck;
  }

  @override
  TestState createState() => _testState;
}

// TestStateは省略
```

TestStateがチェックボックスを管理するstateだったとして、その状態を外部から知りたいような時に、上のようなソースコードが思いつきます。この StatefulWidget は、1度だけ表示させるなら大丈夫ですが、何かの拍子に再描画されると、エラーを起こします。

再描画というと、画面上部のタブを切り替えたあと戻ってくるとか、ボタン押下でウィジェットを切り替えるとかです。

StatefulWidget が1回目に出現する時に、`State(今回だと、TestStateクラス)のインスタンス` ができます。そのまま createState が呼ばれ、Stateが描画されます。しかし、2回目はそうではありません。2回目に表示されるときは、インスタンスの再生成は行われず、createState が直接呼ばれます。そこに渡されるのは、**1回目に既にcreateStateメソッドに渡された使用済みのStateインスタンス** です。

createStateメソッドに、使用済みの古いStateインスタンスを渡すと、もれなくエラーを吐きます。新品好きなんです。

解決策としては、`createState()が呼ばれるたびにStateのインスタンスを再生成する`ようにすれば良いです。

```dart:test.dart
class Test extends StatefulWidget {
  TestState _testState;

  TestState _regene() {
    _testState = TestState();
    return _testState;
  }

  bool isCheck() {
    return _testState.isCheck;
  }

  @override
  TestState createState() => _regene();
}

// TestStateは省略
```

こんな感じで、1つ関数でも挟んであげて、毎回 Stateインスタンスを生成するように書き換えました。こうすれば、createState() に古い Stateインスタンスが渡ることはありません。エラーも解決できます。


# setState を使うことをやめる

flutter 初学者が必ず通るのが、setState を用いた状態管理です。どの本を呼んでも、どの example を見ても、最初に説明されるのは setState です。確かに、ちょっとしたサンプルアプリを作るくらいなら setState は有効です。

setState は、「コンポーネントたちが自分の状態を自分で管理する」という考え方で、最近の状態管理のデファクトスタンダードではありません。例えば、入力フォームを作るにしても、「テキストボックス１つ１つが、テキストボックスのテキスト情報を持っている」というのは、状態管理が大変になります。入力フォームの入力情報を最終的にかき集めてどこかに送信したりするわけですが、そのかき集める作業で一苦労です。setState を軸にして開発をしていくと、その一苦労を絶対に避けられません。どうやっても、コンポーネントが状態を持ちます。

web業界に、vuexとかreduxとか（自分はvuexあまり好きではないですが）、状態管理をするものがあるように、flutter にも BLoCパターンというものがあります。ただ、この BLoCパターンは少し前までは主流でしたが、今（2020/04/26）は Provider というものがGoogleの推奨です。思想的には、redux に似たものがあります。少し最初の入りが難しいですが、慣れてしまえば、setState を使う気にならなくなります。

早めに setState を抜け出して、providerを使うことを心から推奨します。provider の細かい解説などは、気が向いたら書くかも知れません。