「画像そのものを返却する」というAPIを利用したのですが、このレスポンス画像をFlutterで表示させることに苦戦しました。忘れないようにメモを残しておきます。
今回の記事で利用するAPIは以下のように実装しました。
const http = require("http");
const fs = require("fs");
const path = require("path");
const port = 3001;
const server = http.createServer((_, res) => {
res.statusCode = 200;
res.setHeader("Content-Type", "image/png");
const filePath = path.resolve(__dirname, "sample.png");
const file = fs.readFileSync(filePath, "binary");
res.end(file, "binary");
});
server.listen(port, () => {
console.log(`Server running at port ${port}`);
});
以前利用したAPIをNode.jsで再現してみました。内容としてはバイナリ形式で画像を返しているだけです。本来のAPIではPOSTデータをもとに画像の選択を行っていました。
記事を書いている段階で知りましたが、GETメソッド版も用意されていました…
なお画像はこちらのサイトのものを使用しました。
そのまま受け取った場合
用意したAPIをDioパッケージで叩いてみました。
// main.dart
// 一部省略しています
// このコードは動作しません
Future<void> _handlePressed() async {
final dio = Dio();
final response = await dio.post<Uint8List>(
"http://10.0.2.2:3001",
data: {
"fileKey": "INVALID_FILE_KEY",
},
);
setState(() {
_bytes = response.data;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_bytes != null ? Image.memory(_bytes!) : const Text("クリックで画像表示")
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _handlePressed,
child: const Icon(Icons.add),
),
);
}
Imageウィジェットで表示を試みたのですが、これではエラーとなってしまい画像は表示されません。
バイナリ形式で受け取る方法
Dioはレスポンスデータの変換を行ってくれるようなのですが、今回のようなケースでは不要な機能です。ResponseTypeオプションにResponseType.bytes
を指定することでオリジナルのバイトを取得することができるようになります。
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Dio'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Uint8List? _bytes;
Future<void> _handlePressed() async {
final dio = Dio();
final response = await dio.post<Uint8List>("http://10.0.2.2:3001",
data: {
"fileKey": "INVALID_FILE_KEY",
},
// Dioがレスポンスデータをバイナリ形式に変換する
options: Options(responseType: ResponseType.bytes));
setState(() {
_bytes = response.data;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_bytes != null ? Image.memory(_bytes!) : const Text("クリックで画像表示")
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _handlePressed,
child: const Icon(Icons.add),
),
);
}
}
変更したのはdioにオプションを指定した部分のみです。このオプションを指定してフローティングアクションボタンをクリックすると、馬の画像が表示されるようになります。
GETメソッドならImage.networkのみで表示可能
ちなみにGETメソッドで良い場合はImage.network
で表示させることができます。
Image.network("http://10.0.2.2:3001")
まとめ
今回はDioパッケージを利用して、バイナリ形式で返される画像を表示させてみました。
当時は表示方法が分からずかなり悩んでしまいました。公式ドキュメントやGithubのissueには解決策が書いてあった為、これらのドキュメント類をきちんと読むようにしたいと思います。
(そもそもAPIのドキュメントをきちんと読んでいればImage.networkで簡単に表示できていたのに…)記事を書くまで気が付きませんでした。