もふもふ技術部

IT技術系mofmofメディア

【Flutter 連載記事第4回】S3を使って画像アップロード・ダウンロードする

本記事について

本記事はシリーズ連載記事の第4回になります。

今回はAWSのS3に入れた画像データをダウンロードして表示させたり、アプリからS3に画像アップロードさせるような仕様を実装する方法をご紹介しようと思います。

  1. flutterの環境構築
  2. widgetを使った画面表示や、画面遷移をさせる
  3. DBの設定やCRUDをする
  4. S3を使って画像アップロード・ダウンロードする
  5. TODOアプリを作成する

バージョン

  • OS: macOS Monterey 12.6
  • チップ: Apple silicon
  • Flutter SDK: flutter_macos_arm64_3.3.9-stable

S3とFlutterを接続する方法

FlutterではAmplifyを利用してS3に接続する方法が公式としても推奨されており、Amplifyを使う方法を紹介しているネット記事も多くありますが、S3のアクセスキーとシークレットキーだけを使ってシンプルにS3にアクセスしたいというだけだとAmplifyを使うのは少し大掛かりな感じなのと、今回はCognitoの認証機能も使用しないため、今回はシンプルに使える minio_new というライブラリを使用しようと思います。

S3に入れた画像をダウンロードして表示する

まずはminio_newをアプリに入れましょう。

$ flutter pub add minio_new

を実行すると、pubspec.yamlにminio_newが追加されます。

pubspec.yaml

dependencies:
  minio_new: ^1.0.2

S3のIAMやアクセスキーとシークレットキーの発行等の設定部分についてはこの記事では割愛させて頂きますが、ここではS3のバケットの直下にmofmofのロゴ画像「logo.png」を入れた形とします。

minioのS3へ接続するための初期化処理が下記です。regionは東京リージョンの場合を設定しています。必要なパッケージもインポートしています。

import 'package:flutter/material.dart';
import 'package:minio_new/minio.dart';
import 'dart:typed_data';

final minio = Minio(
  endPoint: 's3-ap-northeast-1.amazonaws.com',
  region: 'ap-northeast-1',
  accessKey: 'アクセスキー',
  secretKey: 'シークレットキー',
);

そして、S3に接続して画像を取得する処理が下記です。

Future<Image> getImage() async {
  final stream = await minio.getObject('バケット名', 'logo.png');
  List<int> memory = [];
  await for (var value in stream) {
    memory.addAll(value);
  }
  return Image.memory(Uint8List.fromList(memory));
}

初期化したminioのgetObjectメソッドでS3からデータをダウンロードします。ただ、S3からダウンロードしたデータのままでは Image Widgetで表示させることが出来ないため、Uint8Listというデータの型に変更をかけています。この処理を入れることで、 Image Widgetで画像表示をさせることが出来るようになります。

今回は上記の処理を lib/image_from_s3.dart というファイルを新規作成して実装しました。 lib/image_from_s3.dart の全体が下記になります。

lib/image_from_s3.dart

import 'package:flutter/material.dart';
import 'package:minio_new/minio.dart';
import 'dart:typed_data';

class ImageFromS3 extends StatelessWidget {
  ImageFromS3({super.key});

  final minio = Minio(
    endPoint: 's3-ap-northeast-1.amazonaws.com',
    region: 'ap-northeast-1',
    accessKey: 'アクセスキー',
    secretKey: 'シークレットキー',
  );

  Future<Image> getImage() async {
    final stream = await minio.getObject('バケット名', 'logo.png');
    List<int> memory = [];
    await for (var value in stream) {
      memory.addAll(value);
    }
    return Image.memory(Uint8List.fromList(memory));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('タイトル')),
      body: Center(
        child: FutureBuilder(
          future: getImage(),
          builder: (BuildContext context, AsyncSnapshot<Image> snapshot) {
            if (snapshot.hasData) {
              return Center(child: snapshot.data);
            } else {
              return const Center(
                child: Text('データが取得できていません'),
              );
            }
          },
        ),
      ),
    );
  }
}

lib/main.dart に lib/image_from_s3.dart をインポートしてシミュレータで表示させてみると、

flutter_s3_1

上記のようにS3に入れたロゴ画像をアプリで表示させることが出来ました。

S3に画像をアップロードする

ローカルで持っている画像をS3にアップロードする処理を実装してみましょう。ローカルで画像を使えるようにするには、pubspec.yamlflutter: の項目のところに assets: を設定する必要があります。

pubspec.yaml

flutter:
  assets:
    - images/

上記のように記載すると、imagesディレクトリの中のファイルが全てローカルで使用可能になります。デフォルトの状態ではディレクトリは存在しませんので、Flutterアプリのディレクトリ直下に images ディレクトリを新規に作成します。そして作成したディレクトリに画像を入れます。ここでは logo2.png という名前で画像を入れる形にします。

ここから具体的な実装のコード箇所になりますが、minioの初期化処理はダウンロードの時と同じです。

import 'package:flutter/material.dart';
import 'package:minio_new/minio.dart';
import 'dart:typed_data';

final minio = Minio(
  endPoint: 's3-ap-northeast-1.amazonaws.com',
  region: 'ap-northeast-1',
  accessKey: 'アクセスキー',
  secretKey: 'シークレットキー',
);

そして、S3にファイルをアップロードする処理が下記になります。

void uploadImage() async {
  final byteData = await rootBundle.load('images/logo2.png');
  Stream<Uint8List> imageBytes = Stream.value(byteData.buffer
      .asUint8List(byteData.offsetInBytes, byteData.lengthInBytes));

  await minio.putObject(
    'バケット名',
    'logo2.png',
    imageBytes,
  );
}

ローカルで持っているimagesディレクトリ内のlogo2.pngというファイルを取得します。それをUint8ListのStreamという型に変換した上でminioのputObjectメソッドに渡しています。putObjectに渡せる型がUint8ListのStreamで指定されているため、このような形で処理をしている流れになります。

今回は lib/image_to_s3.dart というファイルを新規作成して上記の処理を実装して、それを lib/main.dart にインポートしました。

lib/image_to_s3.dart の全体が下記になります。

lib/image_to_s3.dart

import 'package:flutter/material.dart';
import 'package:minio_new/minio.dart';
import 'package:flutter/services.dart';

class ImageToS3 extends StatelessWidget {
  ImageToS3({super.key});

  final minio = Minio(
    endPoint: 's3-ap-northeast-1.amazonaws.com',
    region: 'ap-northeast-1',
    accessKey: 'アクセスキー',
    secretKey: 'シークレットキー',
  );

  void uploadImage() async {
    final byteData = await rootBundle.load('images/logo2.png');
    Stream<Uint8List> imageBytes = Stream.value(byteData.buffer
        .asUint8List(byteData.offsetInBytes, byteData.lengthInBytes));

    await minio.putObject(
      'バケット名',
      'logo2.png',
      imageBytes,
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('タイトル')),
      body: Center(
        child: GestureDetector(
          onTap: () {
            uploadImage();
          },
          child: const Text('タップしてアップロード'),
        ),
      ),
    );
  }
}

こちらをシミュレータで表示させて、「タップしてアップロード」のところをタップすると、S3のバケットにファイルがアップロードされることが確認できます。

最後に

今回はS3を使った画像アップロードとダウンロードのご紹介をさせて頂きました。元の画像データをそのままの形式で使うことが出来ずデータ形式の変更をしないといけないのは少し面倒かつややこしいところではあるのですが、画像データの形式の調整さえ上手く出来ればそれ以外の部分については使い方は分かりやすいかなと思います。

参考

AWS S3で追加した画像をFlutterで表示する方法

Flutter画像の表示方法と各形式の変換