Flutter Web: Tips集

最近Flutter Webを利用しています。
調べないとうまくいかなかった事がいくつかあったので、書き残しておきます。

<font color="Red">**Flutter Webに限定した記事です(iOSやAndroidは考慮していません)**</font>

## 画像を表示したい

`web/assets/` 配下に画像を配置します。

`web/assets/icons/sample.png` に配置した場合は

```[dart]
Image sample = Image.asset('icons/sample.png');
```

で利用できるようになります。

別のサーバにある画像の場合は

```
Image sample = Image.network('https://xxx.com/sample.png')
```

で利用できます。

## マテリアルアイコンを表示したい

マテリアルアイコンを使いたい場合はIconsクラスの定数を指定する事で利用できると、公式ドキュメントに書かれてます。
[利用手順](https://api.flutter.dev/flutter/material/Icons-class.html)も書かれていますが、これだと利用できません。

*こうなる*
<img width="55" alt="menu-fail.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423506/375feac9-eed1-ff4c-4dc9-7a1786ac9661.png">

*こうしたい*
<img width="56" alt="menu-success.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423506/253f081e-ffe9-d8a2-4530-ef34c4bd312a.png">

`web/assets/FontManifest.json` に `MaterialIcons` フォントを設定してください。
[Flutter Webでマテリアルアイコンが表示されないとき](https://qiita.com/cheddar_dev/items/859dc10b7783694c2c53)
で利用できるようになります。

## カスタムフォントを利用したい

`web/assets/fonts/sample.otf` 配下に保存した場合、
`web/assets/FontManifest.json` に利用したいフォントを記載します。

```[json]
[
    {
        "family": "sample",
        "fonts": [
            {
                "asset": "fonts/sample.otf"
            }
        ]
    }
]
```

FontFamilyとして `sample` が利用できるようになるので、
このように記述すればカスタムフォントが適用されます。

```[dart]
Text("hoge", style: TextStyle(fontFamily: 'sample'))
```

## 絵文字を表示したい

:grin: `:grin:` はUnicodeで `1F601` なので、

```[dart]
Text("\u{1F601}")
```

で表示できます。

<img width="65" alt="woman-lifting-weights.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423506/26bc4b96-a233-8edb-1211-d9a7215a06e6.png"> `:woman-lifting-weights:` はこう

```[dart]
Text("\u{1F3CB}\u{FE0F}\u{200D}\u{2640}\u{FE0F}")
```

参考)
[Special characters in Flutter](https://stackoverflow.com/questions/50060648/special-characters-in-flutter)
[Slackの絵文字を全て取得する](https://qiita.com/dbgso/items/54cfa5c79d119888747c#%E7%B5%B5%E6%96%87%E5%AD%97%E3%81%A8%E3%81%97%E3%81%A6%E6%8F%8F%E7%94%BB%E3%81%99%E3%82%8B)

## アプリの背景全体をグラデーションさせたい

左のようなデフォルトのマテリアルデザインの配色から、背景全体をグラデーションさせたい場合、

<img width="300" alt="woman-lifting-weights.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423506/b7bf453c-9ab3-553f-6758-032a36386b84.png"> <img width="300" alt="woman-lifting-weights.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423506/5ac336ea-1926-5724-c712-530d86c49a57.png">

こんな感じでテーマとコンテナを設定すると実現できます。

```[dart]
import 'package:flutter_web/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        scaffoldBackgroundColor: Colors.transparent,
        appBarTheme: AppBarTheme(
          elevation: 0.0,
          color: Colors.transparent,
        ),
      ),
      home: Container(
        decoration: BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment.centerLeft,
            end: Alignment.topRight,
            colors: [ Colors.red, Colors.lightGreenAccent,],
          )
        ),
        child: Scaffold(
          appBar: AppBar(
            title: Text('Flutter Demo Home Page'),
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Text('Hello, World!',),
              ],
            ),
          ),
        )
      ),
    );
  }
}
```

デフォルトとの差分はこんな感じになります。

<img width="971" alt="diff.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423506/1b4addd3-3239-8f86-436e-490bf50d04c1.png">

参考) [Flutter: wrapping Scaffold with Container for gradient background](https://stackoverflow.com/questions/53408917/flutter-wrapping-scaffold-with-container-for-gradient-background)

## APIレスポンスの日本語が文字化けする

レスポンスの `body` ではなく `bodyBytes` を利用すると解消できます。

```[dart]
import 'package:http/http.dart' as http;
import 'dart:convert' show utf8;
import 'dart:convert' show json;

main() {
  http.Response response = await http.get('https://xxxx.com/users');

  if ( response.statusCode == 200 ) {
    return json.decode(utf8.decode(response.bodyBytes));
  }
}
```

参考) [Dartのhttpパッケージで文字化け](https://qiita.com/takyam/items/98d6336f1b2fe912fd56)

## OSを判別したい

スマホ向けだと[こんな感じ](https://api.flutter.dev/flutter/dart-io/Platform-class.html#determine-the-os)の記述で実現できるらしいのですが、

```[dart]
import 'dart:io' show Platform;

main(){
  if (Platform.isWindows) {
    print('is a Windows');
  } else {
    print('is not a Windows');
  }
}
```

[Flutter Webだと使えない様子](https://pub.dev/packages?q=io)でした。

仕方がないので、 `dart:html` を使って判別しました。

```[dart]
import 'dart:html';

main(){
  if (window.navigator.platform.contains('Win')) {
    print('is a Windows');
  } else {
    print('is not a Windows');
  }
}
```

## 突然のビルドエラー

原因などわかっていないのですが、突然下記のビルドエラーが出るようになりました。

`[WARNING]build_web_compilers:entrypoint on web/main.dart`

[こちら](https://github.com/dart-lang/angular/issues/1606)の下の方に記載されているように、依存関係のアップグレードからやり直したら復旧しました。

*コマンド部分転記*

依存関係をアップグレードします

```[bash]
pub upgrade
```

生成されたコードを整理します

```[bash]
pub run build_runner clean
```

もう一度コンパイルして、新しいエラーがないか確認します

```[bash]
pub run build_runner serve
```