はじめに
FlutterといえばMobile、Web、Desktop、Embedded Devicesをシングルコードベースで実装できる便利なマルチプラットフォームです。各プラットフォームをシングルコードベースで実装しているパターンはまだまだ少ない気がしますが、これからどのように広がっていくのか楽しみです。
今回はそんなマルチプラットフォームを実装していくときの痒いとこを解消するための記事です。
目的
マルチプラットフォームを進めていく上の問題点はたくさんあると思いますが、今回は各専用ライブラリを使用した時のコンパイルエラーを回避する方法を記事にしました。
具体的なケースだと、import 'dart:html'
が入っているレポジトリをmobileでビルドしようとすると以下のようなコンパイルエラーがでてきます。mobileにはdart:htmlが存在しないので、怒られちゃいます。
僕自身FlutterWebでweb専用ライブラリを使って開発を進めた後に、mobile側でコンパイルすることができず泣く泣くレポジトリを分けて「管理めんどくさぁ」となったことがあります。
結論
export使おう
公式ページにも書いてありましたので、気になる方はみてください。
解説
公式ページから引用してきたやつを簡単に解説します。
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:
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()
がデバイスから写真を呼び出すメソッドになります。
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専用ライブラリを同じレポジトリに共存させるためだったので便宜上必要でした。
class Pick {
Future<Uint8List?> pickFile() async {}
}
if (dart.library.html)
がTrueだった場合は、Pick().pickFile()
は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
を参照します。
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;
}
}
以上のように構成していけば、同じレポジトリで専用ライブラリを使いこなすことができます!
では、いってらっしゃい。