【Flutter】専用ライブラリ使用時のコンパイルエラーを回避する方法

# はじめに
FlutterといえばMobile、Web、Desktop、Embedded Devicesをシングルコードベースで実装できる便利なマルチプラットフォームです。各プラットフォームをシングルコードベースで実装しているパターンはまだまだ少ない気がしますが、これからどのように広がっていくのか楽しみです。

今回はそんなマルチプラットフォームを実装していくときの痒いとこを解消するための記事です。

# 目的
マルチプラットフォームを進めていく上の問題点はたくさんあると思いますが、今回は**各専用ライブラリを使用した時のコンパイルエラーを回避する方法**を記事にしました。

具体的なケースだと、`import 'dart:html'`が入っているレポジトリをmobileでビルドしようとすると以下のようなコンパイルエラーがでてきます。mobileにはdart:htmlが存在しないので、怒られちゃいます。
![スクリーンショット 2021-12-10 12.23.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/923352/ad380d59-e902-0f23-03f9-da544a250084.png)


僕自身FlutterWebでweb専用ライブラリを使って開発を進めた後に、mobile側でコンパイルすることができず泣く泣くレポジトリを分けて「管理めんどくさぁ」となったことがあります。


# 結論
export使おう

公式ページにも書いてありましたので、気になる方はみてください。

https://dart.dev/guides/libraries/create-library-packages#conditionally-importing-and-exporting-library-files

# 解説
公式ページから引用してきたやつを簡単に解説します。

>To conditionally import or export, you need to check for the presence of dart:* libraries. Here’s an example of conditional export code that checks for the presence of dart:io and dart:html:

>```dart:lib/hw_mp.dart 
export 'src/hw_none.dart' // Stub implementation
    if (dart.library.io) 'src/hw_io.dart' // dart:io implementation
    if (dart.library.html) 'src/hw_html.dart'; // dart:html implementation
```


>Here’s what that code does:

>- In an app that can use dart:io (for example, a command-line app), export src/hw_io.dart.
>- In an app that can use dart:html (a web app), export src/hw_html.dart.
>- Otherwise, export src/hw_none.dart.


専用ライブラリを使う場合はimportやexportが利用可能かチェックが必要です。`hw_mp.dart`は公式のサンプルです。

- `dart:io`が使用可能（主にmobile）だと`dart.library.io`はTrueになり、`src/hw_io.dart`を参照
- `dart:html`が使用可能（主にweb）だと`dart.library.html`はTrueになり、`src/hw_html.dart`を参照
- どちらも使用不可なら`src/hw_none.dart` を参照

公式サンプルのようにexportに条件分岐を追記してあげれば、専用ライブラリを使っていたとしてもコンパイルエラーを回避することができます！

実際に使うケースを想定してテストプロジェクトを作ってみたので、具体的にどうやって使うのか参考になればと思います。

# 実践
今回は「デバイスから写真を選択して表示する」というシンプルな機能をmobileとwebで動作するプログラム実装しました。Githubにあげているので気になる方は見てみてください。


https://github.com/MatsumaruTsuyoshi/flutter_mobile_web

実際に作ったアプリが以下画像になります。ファイル選択ボタンを押すと、デバイスから写真を選択して表示します。web専用ライブラリを使ったので、ライブラリの条件分岐を実装しないとmobileビルド時にコンパイルエラーが起きてしまいます。

**※今回のテストプロジェクトではimage_pickerをmobile専用ライブラリかのように扱っている点ご了承ください。**
![スクリーンショット 2021-12-10 13.32.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/923352/29a5e782-2373-9996-9922-5a32f74a26fa.png)


## ファイル構成
lib配下のファイル構成です。
![スクリーンショット 2021-12-10 13.42.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/923352/dbc45a6b-6b90-75be-374f-0cb37f859a19.png)

ここでは、`main.dart`が`pick_export.dart`を参照している点がミソです。

![スクリーンショット 2021-12-10 13.42.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/923352/cf03450a-6173-93db-1949-1a3874566b50.png)

そして、pick_export.dartはさきほどの公式サンプルのような条件分岐を実装してます。

```dart:pick_export.dart
export 'pick.dart'
    if (dart.library.html) 'pick_web.dart' //image_picker_webライブラリを使用
    if (dart.library.io) 'pick_mobile.dart'; //image_pickerライブラリを使用
```

これで専用ライブラリを使いこなすことができます。

## コードの中身
`main.dart`の`Pick().pickFile()`がデバイスから写真を呼び出すメソッドになります。

```dart:main.dart
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'pick_export.dart'; 

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  Uint8List? uint8list;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Column(
        children: [
          Center(
            //ファイル選択ボタン
            child: ElevatedButton(
                onPressed: () async {
                  final _uint8List = await Pick().pickFile(); //ビルドするまではpick.dartを参照
                  setState(() {
                    uint8list = _uint8List;
                  });
                },
                child: const Text('ファイルを選択')),
          ),

          //画像を選択したら表示
          uint8list != null
              ? Expanded(child: Image.memory(uint8list!))
              : const SizedBox()
        ],
      ),
    );
  }
```

ちなみに`pick.dart`は以下のような空メソッドとして定義してます。今回は空メソッドとしましたが、各プラットフォームで使用可能なライブラリであればここに実装してもOKです。今回の想定として、web専用ライブラリとmobile専用ライブラリを同じレポジトリに共存させるためだったので便宜上必要でした。

```dart:pick.dart
class Pick {
  Future<Uint8List?> pickFile() async {}
}
```

`if (dart.library.html) `がTrueだった場合は、`Pick().pickFile()`は`pick_web.dart`を参照します。

```dart:pick_web.dart
import 'package:image_picker_web/image_picker_web.dart';

class Pick {
  Future<Uint8List?> pickFile() async {
    Uint8List? bytesFromPicker =
        (await ImagePickerWeb.getImage(outputType: ImageType.bytes))
            as Uint8List?;
    if (bytesFromPicker == null) return null;
    return bytesFromPicker;
  }
}
```

`if (dart.library.io) `がTrueだった場合は、`Pick().pickFile()`は`pick_mobile.dart`を参照します。


```dart:pick_mobile.dart
import 'package:image_picker/image_picker.dart';

class Pick {
  Future<Uint8List?> pickFile() async {
    final ImagePicker _picker = ImagePicker();
    final XFile? image = await _picker.pickImage(source: ImageSource.gallery);
    if (image == null) return null;
    final uint8List = await image.readAsBytes();
    return uint8List;
  }
}
```

以上のように構成していけば、同じレポジトリで専用ライブラリを使いこなすことができます！

では、いってらっしゃい。