LoginSignup
6
3

More than 5 years have passed since last update.

FlutterでFirebase StorageにアップロードするとiOSではcontent-typeが自動で推測されない

Last updated at Posted at 2019-03-17

気がするんだけど本当?

同じ画像をアップロードした際、iOSではcontent-typeapplication/octet-streamになって、Androidではimage/jpegになった。なぜiOSで推測されないのか気になるから追ってみました。ソースを見てきちんとそうなることを確認したかったのです。

ただ先に言っておくとiOSで推測されないのは確かなので、解決法としては自分でcontent-typeをつけるのがメジャーになるとは思います。この記事はcontent-typeがつかない問題の解決法を教えるのではなく、ソースを追って、確かに推測されてない!!!ってなるための記事です。

Flutterのdartからネイティブソースを呼び出す流れに詳しくない人は、これを読むと こんな感じで呼び出してるんだ〜〜へーーってなれるかも

iOS側の呼び出しを追っていく

dartの StorageReference#putFile の中の _StorageFileUploadTask の中から。たまにオーバーロードしてるメソッドとかは飛ばしてます。

ほないくで

firebase_storage plugin dart - invokeMethod

upload_task.dart
  @override
  Future<dynamic> _platformStart() {
    return FirebaseStorage.channel.invokeMethod( // <- method channelを使って
      'StorageReference#putFile', // <- StorageReference#putFile を蹴りたい
      <String, dynamic>{
        'app': _firebaseStorage.app?.name,
        'bucket': _firebaseStorage.storageBucket,
        'filename': _file.absolute.path,
        'path': _ref.path,
        'metadata':
            _metadata == null ? null : _buildMetadataUploadMap(_metadata),
      },
    );
  }

firebase_storage plugin - create FlutterMethodChannel

FireaseStoragePlugin.m
  FlutterMethodChannel *channel = // 蹴るためにFlutterMethodChannelを作っておく
      [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/firebase_storage"
                                  binaryMessenger:[registrar messenger]];
  FLTFirebaseStoragePlugin *instance = [[FLTFirebaseStoragePlugin alloc] init];
  instance.channel = channel;
  [registrar addMethodCallDelegate:instance channel:channel];
}

firebase_storage plugin - handle method call

FirebaseStoragePlugin.m
  } else if ([@"StorageReference#putFile" isEqualToString:call.method]) {
    [self putFile:call result:result]; // self.putFileを呼び出す
  }

firebase_storage plugin - put file

FirebaseStoragePlugin.m
- (void)putFile:(FlutterMethodCall *)call result:(FlutterResult)result {
  NSData *data = [NSData dataWithContentsOfFile:call.arguments[@"filename"]];
  [self put:data call:call result:result]; // self.putを呼び出す
}

firebase_storage plugin - put

FirebaseStoragePlugin.m
- (void)put:(NSData *)data call:(FlutterMethodCall *)call result:(FlutterResult)result {
  NSString *path = call.arguments[@"path"];
  NSDictionary *metadataDictionary = call.arguments[@"metadata"];
  FIRStorageMetadata *metadata;
  if (![metadataDictionary isEqual:[NSNull null]]) {
    metadata = [self buildMetadataFromDictionary:metadataDictionary];
  }
  FIRStorageReference *fileRef = [storage.reference child:path];
  FIRStorageUploadTask *uploadTask = [fileRef putData:data metadata:metadata]; // FIRStorageReference#putData を呼び出す

  // ...some procedure...
}

firebase-ios-sdk - put data

ここから、flutterのプラグインではなく、firebase-ios-sdk側のソースになります

FIRStorageReference.m

- (FIRStorageUploadTask *)putData:(NSData *)uploadData
                         metadata:(nullable FIRStorageMetadata *)metadata
                       completion:(nullable FIRStorageVoidMetadataError)completion {
  if (!metadata) {
    metadata = [[FIRStorageMetadata alloc] init];
  }

  metadata.path = _path.object;
  metadata.name = [_path.object lastPathComponent];
  FIRStorageUploadTask *task =
      [[FIRStorageUploadTask alloc] initWithReference:self  // FIRStorageUploadTaskを初期化
                                       fetcherService:_storage.fetcherServiceForApp
                                        dispatchQueue:_storage.dispatchQueue
                                                 data:uploadData // 引数がfileじゃなくてdata
                                             metadata:metadata];

  // ...some procedure...
}

元凶:firebase-ios-sdk - init FIRStorageUploadTask with data

FIRStorageUploadTask.m

- (instancetype)initWithReference:(FIRStorageReference *)reference
                   fetcherService:(GTMSessionFetcherService *)service
                    dispatchQueue:(dispatch_queue_t)queue
                             data:(NSData *)uploadData
                         metadata:(FIRStorageMetadata *)metadata {
  self = [super initWithReference:reference fetcherService:service dispatchQueue:queue];
  if (self) {
    _uploadMetadata = [metadata copy];
    _uploadData = [uploadData copy];
    _progress = [NSProgress progressWithTotalUnitCount:[_uploadData length]];

    if (!_uploadMetadata.contentType) {
      _uploadMetadata.contentType = @"application/octet-stream"; // あー!!!!!!!!!!!
    }
  }
  return self;
}

ちなみに

FIRStorageReference#putDataじゃなくてputFileを呼び出してると以下のソースにたどり着き、きちんとcontent-typeがつく

FIRStorageUploadTask.m

- (instancetype)initWithReference:(FIRStorageReference *)reference
                   fetcherService:(GTMSessionFetcherService *)service
                    dispatchQueue:(dispatch_queue_t)queue
                             file:(NSURL *)fileURL // <- dataじゃなくてfile
                         metadata:(FIRStorageMetadata *)metadata {
  self = [super initWithReference:reference fetcherService:service dispatchQueue:queue];
  if (self) {
    _uploadMetadata = [metadata copy];
    _fileURL = [fileURL copy];
    _progress = [NSProgress progressWithTotalUnitCount:0];

    // イケメン
    NSString *mimeType = [FIRStorageUtils MIMETypeForExtension:[_fileURL pathExtension]];

    if (!_uploadMetadata.contentType) {
      _uploadMetadata.contentType = mimeType ?: @"application/octet-stream";
    }
  }
  return self;
}

firebase docでは

putFile: メソッドは NSURL ファイル名拡張子からコンテンツ タイプを自動的に推測します

って書いてるが、flutterのplugin自体がそっち呼んでないから推測しないんだなぁ〜

Androidは?

力尽きた

雑記

firebase-ios-sdkはTags見たらそれっぽいバージョンあったからそのURL引っ張ってきたんだけど、flutterの方はそれっぽいバージョン見つけられなくてmasterになっちゃってる。バージョンとかでTag切られてないのかな?よくわかんないっす

6
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3