【Flutter】material.ioのadaptive系ライブラリを使用してレスポンシブなポケモン図鑑を作る

## 概要

Material Designの親である[material.io](https://material.io/)がFlutterで出しているライブラリに以下のものがあります。
- [adaptive_breakpoints](https://pub.dev/packages/adaptive_breakpoints)
- [adaptive_components](https://pub.dev/packages/adaptive_components)
- [adaptive_navigation](https://pub.dev/packages/adaptive_navigation)

これらはMaterial Designのレスポンシブ対応をより便利にするものです。
通常のアプリを作る際はあまり出番はないかもしれませんが、Webアプリを作る場合などで重宝しそうです。

今回はこちらのライブラリ群を使用して、レスポンシブなポケモン図鑑を作ります。

## ライブラリの説明

### [adaptive_breakpoints](https://pub.dev/packages/adaptive_breakpoints)
公式の説明から。

```
The adaptive_breakpoints package for Flutter allows you to use the adaptive breakpoints entries from the Material Design System.
```

このパッケージには、Material Designの[Breakpoints](https://material.io/design/layout/responsive-layout-grid.html#breakpoints)、つまり画面サイズの定義が含まれています。
このパッケージを単体で使うことはあまりないと思いますが、これから紹介するパッケージ群の実装に大きく関わっているため、存在を覚えておくことは大事でしょう。

### [adaptive_components](https://pub.dev/packages/adaptive_components)

公式の説明から。

```
The adaptive_components package for Flutter provides a set of widgets (e.g. AdaptiveColumn, AdaptiveContainer) used to implement a responsive grid layout in Material Design.
```

[レスポンシブなグリッドデザイン](https://material.io/design/layout/responsive-layout-grid.html)を実現するためのパッケージです。
以下のコンポーネントを提供します。


- adaptive_container
- adaptive_column

#### adaptive_container

**画面サイズによって表示/非表示を変えるContainer**になります。 constraintsに指定した画面サイズの制約に従って表示/非表示を切り替え、表示する場合はchildに指定したコンポーネントを、非表示の場合は縦横が0のLimitedBoxを返します。

```dart
        // 切り替え部分
        if (constraints.withinAdaptiveConstraint(context)) {
          return Container(
            alignment: alignment,
            padding: padding,
            color: color,
            decoration: decoration,
            foregroundDecoration: foregroundDecoration,
            transform: transform,
            clipBehavior: clipBehavior,
            height: height,
            margin: margin,
            child: child,
          );
        } else {
          /// Since this container is not within the adaptive constraints.
          /// No widget must be returned but since you can't return no widget we
          /// are returning a [LimitedBox] which is a very efficent widget.
          return LimitedBox(
            maxWidth: 0.0,
            maxHeight: 0.0,
            child: ConstrainedBox(constraints: const BoxConstraints.expand()),
          );
        }
```


#### adaptive_column

上記のAdaptiveContainerと共に使用されるコンポーネントで、画面サイズとAdaptiveContainerに設定した`columnSpan`の値に応じてレスポンシブなグリッドデザインを作ります。

[パッケージのsample](https://github.com/material-components/material-components-flutter-adaptive/blob/develop/adaptive_components/example/lib/adaptive_column_example.dart)を動かすと以下のような感じになります。
列ごとのColumnの最大数がスマホサイズは4、タブレットは8〜12、それ以上は12となっており、`columnSpan`の合計値に沿うようにレイアウトが変更されています。

![adaptive_column.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99605/38a45278-3848-1685-ac2b-1320c3adbb1b.gif)

(途中columnSpanがContainerの高さが変わっているのはテキストによるものです。)

### [adaptive_navigation](https://pub.dev/packages/adaptive_navigation)

[ナビゲーションドロワー](https://material.io/components/navigation-drawer)をレスポンシブ対応するためのコンポーネントです。Component swappingの欄で示されているように、スマホサイズ、タブレットサイズ、それ以上によってNavigationの位置や大きさが変わります。


## 実装について

今回は[adaptive_components](https://pub.dev/packages/adaptive_components)を使用し、ポケモンの一覧を表示してみます。

- [PokeAPI](https://pokeapi.co/)からデータ取得
- Riverpod + StateNotifierのMVVM構成
- `adaptive_components`を使用してリスト表示

## できたもの

https://github.com/alpha2048/adaptive_pokemon_list_sample

![adaptive_pokemon_list.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99605/bd747f7d-432f-e05c-c256-91dfb2670601.gif)


`columnSpan`を2に設定しているため、スマホサイズで1列に2つ、最大サイズで6つ表示されます。

## 気になった点

#### 他のWidgetが横にある時に通常よりも1〜2個少なく表示される
[内部では`MediaQuery`(画面サイズ)の横幅をそのまま使っているため](https://github.com/material-components/material-components-flutter-adaptive/blob/0a4cecd5bf770ac23e201234e46abf8635347093/adaptive_components/lib/src/adaptive_column.dart#L109)、他にWidgetが横にあっても、その分が考慮されず、場所がずれます。
以下はnavigationを表示した場合。

![adaptive_pokemon_list_navigation.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99605/9421bda9-01a8-a3ce-e24a-24dfe7cf6553.png)


#### itemの数が中途半端だと、切り捨てられる動きがある

初代ポケモンの151匹で表示しようとしたところ、ミュウなどの最後付近のポケモンが表示されないことがあります。[列ごとのColumnSpanが最大値にならないと列にならないようになってる](https://github.com/material-components/material-components-flutter-adaptive/blob/0a4cecd5bf770ac23e201234e46abf8635347093/adaptive_components/lib/src/adaptive_column.dart#L100)ため、全ての画面サイズに対応させる場合はアイテムの数を調整しましょう。今回はマグマラシ(No.156)までのジョウト地方のポケモンにも出張してもらいました。


## おわりに

現在はあまりアップデートがされてないようですが...状況によってはかなり有用なライブラリだと思うので、今後の発展に期待したいところです🙏