【Flutter】使い勝手の良かったパッケージ5選

# はじめに
業務ではバックエンド開発を行っており、趣味でFlutterを触っています。
Flutterで開発を行う際に、[pub.dev](https://pub.dev/)で良さげなパッケージを探すことは少なくはないのでしょうか。そこで、本稿では個人的に使ってみて良かったパッケージを5つ紹介します。

# もくじ
- [カルーセル：carousel_slider](https://qiita.com/naoya_s/items/f96d849be83e3d2c619f#carousel_slider)
- [スプラッシュスクリーン：flutter_native_splash](https://qiita.com/naoya_s/items/f96d849be83e3d2c619f#flutter_native_splash)
- [URLサポート：url_launcher](https://qiita.com/naoya_s/items/f96d849be83e3d2c619f#url_launcher)
- [スケルトンスクリーン：shimmer](https://qiita.com/naoya_s/items/f96d849be83e3d2c619f#shimmer)
- [ページネーション：paginate_firestore](https://qiita.com/naoya_s/items/f96d849be83e3d2c619f#paginate_firestore)

# 環境
```bash
$ flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 2.0.6, on macOS 11.4 20F71
    darwin-x64, locale ja-JP)
[✓] Android toolchain - develop for Android devices (Android
    SDK version 29.0.3)
[✓] Xcode - develop for iOS and macOS
[✓] Chrome - develop for the web
[✓] Android Studio (version 3.5)
[✓] VS Code (version 1.61.1)
[✓] Connected device (2 available)

$ dart --version
Dart SDK version: 2.10.4 (stable) (Wed Nov 11 13:35:58 2020 +0100) on "macos_x64"
```

# carousel_slider
無限スクロールやカスタムウィジェットの利用、オートプレイが行えます。
また、オプションによってループさせたりアクティブとなった画像のスケールアップなどが可能となります。

https://pub.dev/packages/carousel_slider

## 導入例
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107176/3b6fed14-0ba0-0b2e-d6d9-d7fe89e476c0.gif" width="60%">

`CarouselSlider`クラスのフィールドで保持している`CarouselOptions `によってオプションの指定ができます。

```dart:carousel_slider.dart
class CarouselSlider extends StatefulWidget {
  /// [CarouselOptions] to create a [CarouselState] with
  ///
  /// This property must not be null
  final CarouselOptions options;

  /// The widgets to be shown in the carousel of default constructor
  final List<Widget> items;

  /// The widget item builder that will be used to build item on demand
  /// The third argument is the PageView's real index, can be used to cooperate
  /// with Hero.
  final ExtendedIndexedWidgetBuilder itemBuilder;

  /// A [MapController], used to control the map.
  final CarouselControllerImpl _carouselController;

  final int itemCount;

  CarouselSlider(
      {@required this.items,
      @required this.options,
      carouselController,
      Key key})
      : itemBuilder = null,
        itemCount = items != null ? items.length : 0,
        _carouselController = carouselController ?? CarouselController(),
        super(key: key);

  /// The on demand item builder constructor
  CarouselSlider.builder(
      {@required this.itemCount,
      @required this.itemBuilder,
      @required this.options,
      carouselController,
      Key key})
      : items = null,
        _carouselController = carouselController ?? CarouselController(),
        super(key: key);

  @override
  CarouselSliderState createState() => CarouselSliderState(_carouselController);
}
```

デフォルトコンストラクタでは以下のような設定がされています。

```dart:carousel_options.dart
import 'package:flutter/material.dart';

enum CarouselPageChangedReason { timed, manual, controller }

enum CenterPageEnlargeStrategy { scale, height }

class CarouselOptions {
  //　コンストラクタのみ抜粋
  CarouselOptions({
    this.height,
    this.aspectRatio: 16 / 9,
    this.viewportFraction: 0.8,
    this.initialPage: 0,
    this.enableInfiniteScroll: true,
    this.reverse: false,
    this.autoPlay: false,
    this.autoPlayInterval: const Duration(seconds: 4),
    this.autoPlayAnimationDuration = const Duration(milliseconds: 800),
    this.autoPlayCurve: Curves.fastOutSlowIn,
    this.enlargeCenterPage = false,
    this.onPageChanged,
    this.onScrolled,
    this.scrollPhysics,
    this.pageSnapping = true,
    this.scrollDirection: Axis.horizontal,
    this.pauseAutoPlayOnTouch: true,
    this.pauseAutoPlayOnManualNavigate: true,
    this.pauseAutoPlayInFiniteScroll: false,
    this.pageViewKey,
    this.enlargeStrategy: CenterPageEnlargeStrategy.scale,
    this.disableCenter: false,
  });
}
```

導入例は以下のようにカルーセルを実装しており、`enlargeCenterPage`・`autoPlay`・`enlargeCenterPage`を変更しています。

```dart:sample.dart
@override
Widget build(BuildContext context) {
return Container(
  child: CarouselSlider.builder(
    options: CarouselOptions(
      height: 160.0,
      autoPlay: true,
      disableCenter: true,
      enlargeCenterPage: true,
    ),
    itemCount: documents.length,
    itemBuilder: (BuildContext context, int index, int realIndex) {
    // 省略
}
```

# flutter_native_splash
アプリ起動時にデフォルトだと真っ白な画面が表示されます。
そこで、好みの色や画像を表示させるためにこちらのパッケージを利用します。

https://pub.dev/packages/flutter_native_splash

このパッケージは、Dartファイル上でimportして使うのではなく`pubspec.yaml`か`flutter_native_splash.yaml`に設定を記述することで適応できます。
背景色や画像以外にも、ダークテーマ時の設定やOS毎で位置設定もできます。
詳細はドキュメントを御覧ください。

```yaml:pubspec.yaml
dev_dependencies:
  flutter_native_splash: ^1.3.2

flutter_native_splash:
  #背景色の指定
  color: "#42a5f5"
  #背景画像の指定
  background_image: "assets/background.png"
```

設定を記述したあとは、以下コマンドを実行します。

```bash
$ flutter pub run flutter_native_splash:create
```

ちなみに、[pub.dev](https://pub.dev/packages/flutter_native_splash)で動画によるチュートリアルも紹介されています。

https://www.youtube.com/watch?v=8ME8Czqc-Oc


# url_launcher
外部ブラウザでURLにアクセスする以外にも、スキーマを指定することで電話の発信・メール作成・SMS送信もできるようになります。

https://pub.dev/packages/url_launcher

```dart:公式のサンプル
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';

const String _url = 'https://flutter.dev';

void main() => runApp(
      const MaterialApp(
        home: Material(
          child: Center(
            child: RaisedButton(
              onPressed: _launchURL,
              child: Text('Show Flutter homepage'),
            ),
          ),
        ),
      ),
    );

void _launchURL() async {
  if (!await launch(_url)) throw 'Could not launch $_url';
}
```

# shimmer
コンテンツの読み込み中にワイヤーフレームのようなウェジェットを表示させ、スケルトンスクリーンを簡単に実装することができます。

https://pub.dev/packages/shimmer

# 導入例
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107176/ed4b293f-bd4b-9a11-d489-4d4dd19fbd2e.gif" width="60%">

同じくスケルトンスクリーンを導入する際に検討したパッケージに`flutter_shimmer`がありました。

https://pub.dev/packages/flutter_shimmer

個人的に感じた両パッケージの違いは、柔軟性の有無にあるかと思います。
導入例でも使っていた`shimmer`のほうがより柔軟な印象でした。
`Shimmer`は適応するウィジェットを自作できるのに対し、`flutter_shimmer`はパッケージで準備されたモード（典型的なUIの雛形のようなもの）を選択します。`flutter_shimmer`のほうが導入は簡単ですが、読み込み後に表示されるウィジェットとデザインの差異が生じることがあるので、実装コストやデザインを天秤にかけて採用パッケージを決めると良いかもしれません。

以下は`shimmer`を使った実装の例です。
読み込み後に表示させるウィジェットを元にSizeBoxでサイズを指定することで、違和感なく見た目を整えることができます。

```dart:sample.dart
import 'package:shimmer/shimmer.dart';

class ShimmerImage extends StatelessWidget {
  const ShimmerImage({
    Key key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: EdgeInsets.symmetric(horizontal: 10),
      child: ClipRRect(
        borderRadius: BorderRadius.circular(4.0),
        child: SizedBox(
          width: 280,
          height: 160,
          child: Shimmer.fromColors(
            baseColor: Colors.grey[300],
            highlightColor: Colors.grey[100],
            child: Container(
              color: Colors.grey,
            ),
          ),
        ),
      ),
    );
  }
}
```

# paginate_firestore
パッケージ内部でFireStoreをいい感じにラップしてくれて、ページネーション機能を簡単に使えるようにしてくれます。

https://pub.dev/packages/paginate_firestore

`itemsPerPage`でページあたりの読み込み数を指定し、`itemBuilderType`で以下の3つからビルダータイプを選択します。

```dart:paginate_firestore.dart
enum PaginateBuilderType { listView, gridView, pageView }
```

簡単な実装例は以下の通りです。

```dart:sample.dart
class WatchedWorkList extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: PaginateFirestore(
        itemsPerPage: 10,
        itemBuilderType: PaginateBuilderType.listView,
        shrinkWrap: true,
        itemBuilder: (index, context, documentSnapshot) {
          final data = documentSnapshot.data();
          Work work = Work.fromMap(data);
         // 省略
        }
        query: FirebaseFirestore.instance
            .collection('user')
            .doc("xxxxxxxxxxxxxxxxxxxxx")
            .collection("watched")
            .orderBy('id'),
        isLive: true,
      ),
    );
  }
}

```

# 最後に
パッケージを導入する際は`Null safety`に対応していることや、`Flutter Favorite`バッチの有無にも注目してみると良いかもしれません。