【Flutter】「なんか....このアプリ重くない？」と言われないために

## はじめに
リンクラフトでエンジニアとして働いている工藤です！
リンクラフトアドベントカレンダーの19日目を担当します。

今回は、私がFlutter開発を行う上で「なんか....このアプリ重くない？」と言われないために意識していることを3つ紹介させていただきます！

- [非同期処理で可能なものは並列実行する](https://qiita.com/kudo_linc/private/266980d2442190e8ecdc#%E9%9D%9E%E5%90%8C%E6%9C%9F%E5%87%A6%E7%90%86%E3%81%A7%E5%8F%AF%E8%83%BD%E3%81%AA%E3%82%82%E3%81%AE%E3%81%AF%E4%B8%A6%E5%88%97%E5%AE%9F%E8%A1%8C%E3%81%99%E3%82%8B)
- [使い回すデータはできる限りキャッシュ化する](https://qiita.com/kudo_linc/private/266980d2442190e8ecdc#%E4%BD%BF%E3%81%84%E5%9B%9E%E3%81%99%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AF%E3%81%A7%E3%81%8D%E3%82%8B%E9%99%90%E3%82%8A%E3%82%AD%E3%83%A3%E3%83%83%E3%82%B7%E3%83%A5%E5%8C%96%E3%81%99%E3%82%8B)
- [リストを実装する際はメモリ効率を考える](https://qiita.com/kudo_linc/private/266980d2442190e8ecdc#%E3%83%AA%E3%82%B9%E3%83%88%E3%82%92%E5%AE%9F%E8%A3%85%E3%81%99%E3%82%8B%E9%9A%9B%E3%81%AF%E3%83%A1%E3%83%A2%E3%83%AA%E5%8A%B9%E7%8E%87%E3%82%92%E8%80%83%E3%81%88%E3%82%8B)

## 非同期処理で可能なものは並列実行する
メモリ負荷などの問題で悪影響が生じる場合を除き、並列実行に切り替えることで総処理時間を短縮できる可能性が高いため、問題なく並列実行できる非同期処理については`Future.wait()`を使用して並列実行化することを検討しましょう！

```dart
// 直列実行
void fetchData() async {
    // ここから
    await _fetchFirst(); // 1秒かかる処理
    await _fetchSecond(); // 1秒かかる処理
    // ここまで計2秒
}

// 並列実行
void fetchData() async {
    // ここから
    await Future.wait(
        _fetchFirst(); // 1秒かかる処理
        _fetchSecond(); // 1秒かかる処理
      );
    // ここまで計1秒
}
```
## 使い回すデータはできる限りキャッシュ化する
アプリ起動中ずっと変わらないような値を、毎回非同期で取得していたりしませんでしょうか？
こういったデータは可能な限りキャッシュ化しておくと、パフォーマンス向上に役立ちます。

私が参画していたプロジェクトでは、[flutter_secure_storage](https://pub.dev/packages/flutter_secure_storage)を使用して読み取るデータをキャッシュ化していないことで、パフォーマンスが低下していました。
(flutter_secure_storageは、端末内部にデータを保存したり、保存したデータを読み取るために使用されるパッケージです。)

このパッケージは保存するデータを暗号化することができるのですが、暗号化したデータを複号し読み取る際に0.5秒以上の時間がかかっていました。
画面情報を取得するための、APIに渡すユーザー識別IDなどのデータを都度非同期で読み取っていましたので、キャッシュ化によって多くの画面の読み込みが0.5秒以上縮まりました。

```dart
final _instance = const FlutterSecureStorage();
final Map<String, String?> _cache = {};

Future<void> _write({
    required String key,
    required String value,
}) async {
    await _instance.write(key: key, value: value, aOptions: _getAndroidOptions());
    /// 書き込み時にキャッシュを更新
    _cache[key] = value;
}

Future<String?> _read({required String key}) async {
    /// キャッシュがない場合のみ、非同期で値を取得する
    return _cache[key] ??=
        await _instance.read(key: key, aOptions: _getAndroidOptions());
}
```
他にもAPIレスポンスや画像データなど、キャッシュ化することでパフォーマンスの改善が見込めるものは様々あると思いますので、実装時には意識したいですね！

## リストを実装する際はメモリ効率を考える
リストを実装する際、画面外にある項目のメモリを解放するかどうかは、パフォーマンスに大きく影響します。

画面外の項目を保持したままにすれば、再度画面内に表示するときの処理は高速になります。
一方、項目数が非常に多かったり、画像を多く含むリストでは、メモリを圧迫して処理が重くなる要因になります。
表示する内容や項目の多さによって、適切な実装手段を選択するようにしましょう！
```dart
SingleChildScrollView(
    child: Column(
        children: <Widget>[
        /// 画面外に出てもメモリが保持される表示内容
        ],
    ),
)

ListView.builder(
    itemCount: list.length,
    itemBuilder: (context, index) {
        /// 画面外に出るとメモリが破棄される表示内容
    },
)
```
↑あくまで一例として記載しましたが、メモリを解放するものしないものそれぞれ実装方法は多々ありますので、実現したいUIに合わせて、適切なものを選択してください。
複数のリストを組み合わせる、Gridを含むなどの複雑な画面構成で、画面外のメモリを解放したい場合は[CustomScrollView](https://api.flutter.dev/flutter/widgets/CustomScrollView-class.html)を使用するのがおすすめですよ！

## さいごに
いかがでしたでしょうか？
パフォーマンスの問題を挙げ始めるとキリがありませんが、今回は「アプリ開発初心者がまず意識しておくべきポイント」という視点で、3つ紹介させていただきました。
Flutter記事として書きましたが、どれもアプリ実装であれば意識する必要があることだと思います。
これからアプリ開発を始める方や、パフォーマンスをあまり意識していなかった方は、ぜひ今回のポイントを参考にしてみてください！
##
一緒に働く仲間を募集中です！

リンクラフト株式会社では、組織拡大に伴い積極的な採用活動を行っています。
少しでも興味がある方はぜひご連絡ください。

▽会社ホームページ
https://lincraft.co.jp/
▽Instagram
https://www.instagram.com/lincraft.inc/
▽ご応募はこちらより
https://lincraft.co.jp/recruit

※カジュアル面談も受付中です。ご希望の方はHPのお問い合わせフォームよりご連絡ください。