miyasakura’s diary

日記です。

ExpoでActive StorageにDirect Upload

Reac Native勉強中。

APIサーバーでもRailsは大活躍しておりますが、ExpoでActive Storageを扱うにはちょっとコツが必要でした。

まずはImage Pickerはなんでも良いと思うのですが、複数画像を選択できるnpmが expo-image-picker-multiple くらいだったのでこちらをインストール。

yarn add expo-image-picker-multiple

これを呼び出すわけですが、callbackで画像を扱いやすいサイズに縮小しつつ、その情報をbase64で取得します。

保存されたファイルをfetchして扱うとなぜかバイナリが変更されてしまうので、このbase64を取り回す必要がありました。

ついでにファイルサイズとmd5も取得します。

import { ImageBrowser } from 'expo-image-picker-multiple';
import * as ImageManipulator from 'expo-image-manipulator';
import * as FileSystem from 'expo-file-system';

async function processImageAsync(uri) {
  const file = await ImageManipulator.manipulateAsync(
    uri,
    [{ resize: { width: 1000 } }],
    { compress: 0.8, format: ImageManipulator.SaveFormat.JPEG, base64: true },
  );
  return file;
}

    <ImageBrowser
      max={20}
      loadCount={50}
      onChange={(num, onSubmit) => {
        setNum(num);
        setOnSubmit({
          fn: onSubmit,
        });
      }}
      callback={(callback) => {
        setLoading(true);
        callback
          .then(async (photos) => {
            const cPhotos = [];
            for (const photo of photos) {
              const pPhoto = await processImageAsync(photo.uri);
              const info = await FileSystem.getInfoAsync(pPhoto.uri, {
                md5: true,
                size: true,
              });
              cPhotos.push({
                url: pPhoto.uri,
                filename: photo.filename,
                width: pPhoto.width,
                height: pPhoto.height,
                type: 'image/jpg',
                size: info.size,
                checksum: info.md5,
                base64: pPhoto.base64,
              });
            }
            setValue('imageFiles', cPhotos);
            navigation.goBack();
          })
          .catch((e) => {
            console.log(e);
            setLoading(false);
          });
      }}
    />

この情報を利用してRailsAPIを叩きます。

今回recordを作成するのですがAPIは下記のように実際にrecordを作成するのとActiveStorage用のAPIは2つを利用します。

  blob: async (auth: Auth, file: File): Promise<BlobData> => {
    const client = new ApiClient<BlobData>(auth);
    return await client.create('/rails/active_storage/direct_uploads', {
      blob: {
        filename: file.filename,
        contentType: file.type,
        byteSize: file.size,
        checksum: base64.fromByteArray(hexStringToByte(file.checksum)),
      },
    });
  },
  directUpload: async (file: File, blob: BlobData): Promise<void> => {
    const fileBlob = base64.toByteArray(file.base64);
    const options = {
      headers: {
        'Content-Type': blob.directUpload.headers.ContentType,
        'Content-MD5': blob.directUpload.headers.ContentMD5,
      },
    };
    await axios.put(blob.directUpload.url, fileBlob, options);
  },
  create: async (auth: Auth, form: RecordForm): Promise<Record> => {
    const client = new ApiClient<Record>(auth);
    console.log({ record: form });
    return await client.create('/api/records', { record: form });
  },

これを下記のように

  • 画像ごとにactive storageのblobを作成してその情報でdirectUploadする
  • 画像情報を加えてアップロード

という感じでやればOKです。

            const images = await Promise.all(
             imageFiles.map(async (imageFile) => {
                const blob = await RecordApi.blob(state.auth, imageFile);
                await RecordApi.directUpload(imageFile, blob);
                return blob.signedId;
              }),
            );
              await RecordApi.create(state.auth, {
                ...form,
                images,
              });

だいぶ適当でしたが参考までに。

#アップロード中の途中経過を出すのはいったんパスしました。