ユーザー登録系のアプリでプロフィール画像を保存するためにFlutterからLaravel経由で画像をS3にアップロードする必要があったのですが、実装する時にかなり詰まったので投稿しました。
開発環境
Laravel Framework 7.30.6
Flutter 3.0.3
全体の流れ
全体の流れとしては
Flutter側で画像選択 → 選択した画像をbase64エンコードしてLaravel側に送信 → Laravel側でbase64エンコードされた画像を受け取ってデコード → S3にアップロード
という流れになります。
ちなみにですが、別にわざわざbase64エンコードしてテキストに変換してから送信しなくても画像アップロードはできると思うのですが、色々試してもうまくいかなかったので今回はbase64エンコードすることにしました。
(もっと簡単な方法があるならとても知りたい。。)
Flutterアプリ側の実装
画像のアップロードはimage_pickerを使用しました。
image_pickerを使うことでカメラロールから画像を選択したり、撮影した写真をアプリで使用することができます。
image_pickerの使用には色々と設定が必要なのですが、image_pickerの設定については別の記事を参考にしてください。
flutterアプリでカメラロールから画像を選択してアップロードする部分については下記のような形になります。
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:http/http.dart' as http;
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
class UploadImageSample extends StatefulWidget {
const UploadImageSample({Key? key}) : super(key: key);
@override
UploadImageSampleState createState() => UploadImageSampleState();
}
class UploadImageSampleState extends State<UploadImageSample> {
final picker = ImagePicker();
File? image;
Future<void> _setImage() async {
final pickedFile = await picker.pickImage(source: ImageSource.gallery);
setState(() {
if (pickedFile != null) {
image = File(pickedFile.path);
}
});
}
Future<void> _upload() async {
if (image == null) return;
// ここで画像をFileからBase64(String)にエンコードする
Uint8List imageBytes = image!.readAsBytesSync();
String imageBytesBase64 = base64Encode(imageBytes);
// Laravel側のエンドポイントURL(環境に合わせて設定してください)
Uri url = Uri.parse("https://hogegoge.jp");
String body = jsonEncode({image: imageBytesBase64});
// LaravelにPOST
await http.post(url, body: body);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
// タップするとカメラロールが開いて画像を選択するボタン
ElevatedButton(
onPressed: () {
_setImage();
},
child: const Text("画像選択")),
// タップすると選択した画像をLaravelに送信するボタン
ElevatedButton(
onPressed: () {
_upload();
},
child: const Text("アップロードする")),
],
));
}
}
Laravel側の実装
次にLaravel側の実装です。
flutter側の実装で設定したURLを叩くと下記UploadController.phpのuploadメソッドが動く想定です。
public function upload(Request $request){
$image_base64 = $request->input("image");
// 画像名をランダムで作成(本当は拡張子判定とかするんだろうけどとりあえずjpegで固定)
$image_name = Str::random(20) . '.' . 'jpeg';
// デコードする前に不要な文字列を置換する
$image_base64 = str_replace('data:image/jpeg;base64,', '', $image_base64);
$image_base64 = str_replace(' ', '+', $image_base64);
$image_file = base64_decode($image_base64);
// S3に保存
$upload_path = "img/hogehoge"
Storage::disk('s3')->put($upload_path, $image_file, 'public');
// 読み取り用のURLを取得
$url = Storage::disk('s3')->url($upload_path);
// 取得したURLを返却
return response()->json([
"url" => $url,
]);
}
これで無事アップロードができるはずです。