読者です 読者をやめる 読者になる 読者になる

taiki-t's diary

あいうえお

React Native から S3に画像アップロード

という記事を雑にかいた。

taiki-t.hatenablog.com

コードは参考になると良いと思う。

Presigned post to aws s3 from React Native

It took a longtime to figure out how to upload images to aws s3 using presigned post from React Native.

The key part is to use FormData to construct a payload and post it with POST http method. PUTing FormData payload with pre-signed URL didn’t work for me at least.

Server Side

I use Ruby on Rails for server side here. Read Direct to S3 Image Uploads in Rails | Heroku Dev Center to fully understand the code below.

FYI: You don’t need to setup CORS settings in the article for native apps.

The security model for XMLHttpRequest is different than on web as there is no concept of CORS in native apps. - https://facebook.github.io/react-native/docs/network.html

Here is the code:

# In your controller
def presigned_url
  options = {
    key: "uploads/photos/#{SecureRandom.uuid}/${filename}",
    success_action_status: '201',
    acl: 'public-read'
  }
  presigned_post = S3_BUCKET.presigned_post(options)

  # FIXME: http://stackoverflow.com/a/36941996/2930161
  if presigned_post
    render plain: { presignedPost: { fields: presigned_post.fields, url: presigned_post.url } }.to_json, status: 200, content_type: 'application/json'
  else
    render plain: { error: 'No presigned urls.' }.to_json, status: 422, content_type: 'application/json'
  end
end

React Native

// In your component.
async _uploadToS3() {
  try {
    // implement your method to fetch a presigned post object.
    const presignedPost = await this._fetchPreSignedPost()

    let formData = new FormData();

    Object.keys(presignedPost.fields).forEach((key) => {
      formData.append(key, presignedPost.fields[key]);
    });

    formData.append('file', {
      // Provide an url of a target image.
      uri: this.image.uri,
      type: 'image/jpeg',
      name: 'image.jpg',
    })

    const presignedUrl = presignedPost.url

    fetch(presignedUrl, { method: 'POST', body: formData, headers: { 'Content-Type': 'multipart/form-data'} })
  }
  catch (error) {
    console.log(error)
  }
}

This Answer on StackOverflow inspired me for the code, thanks:

stackoverflow.com

Other Links

http://docs.aws.amazon.com/sdkforruby/api/Aws/S3/Bucket.html#presigned_post-instance_method

http://docs.aws.amazon.com/sdkforruby/api/Aws/S3/PresignedPost.html

Upload an Object Using a Pre-Signed URL (AWS SDK for Ruby) - Amazon Simple Storage Service

react-native/XHRExampleFormData.js at master · facebook/react-native · GitHub

Make commit messages great again.

github.com

github.com

github.com

https://github.com/search?p=1&q=make+again&ref=searchresults&type=Commits&utf8=%E2%9C%93

React Nativeのリリース間隔は月次

facebook.github.io

2017/1 から。以前は2週間に一度だった。月末に新しいバージョンがリリースされるので、月の初めには新しいバージョンが利用可能になっている。また、リリース時にはすでにFacebookのプロダクションで2週間ほど運用した状態であるだろうとのこと。

Relay: containerのinitialVariablesに値を渡す

Introduction

非同期処理で何かごにょごにょした後、Relayにその値を使ってサーバーにリクエストを投げてほしかった。

やり方

Relay.createContainerを関数でラップする

雑な実装例:

componentDidMount() {
  this._asyncFunc()
}

async _asyncFunc() {
  try {
    let asyncVal = await someAsyncThings()
    this.setState({asyncVal})
  }
  catch (error) {
    console.log(error)
  }
}

render () {
  let component = null
  if (this.state.asyncVal) {
    component = (
      <Relay.RootContainer
        Component={createRelayComponent(this.state.asyncVal)}
        route={yourRoute}
        renderLoading={function() {
          return <div>Loading...</div>;
        }}
      />
    )
  } else {
    component = <div>Loading...</div>;
  }
  return component
}

function createRelayComponent(asyncVal) {
  return (
    Relay.createContainer(Component, { 
      initialVariables: {
        yourVar: asyncVal
      }

      fragments: { 
        ...
      }
    })
  )
}

参考リンク

Pass in React Component to Relay Variables and get a fragment from it. · Issue #775 · facebook/relay · GitHub

感想

他のやり方があれば教えほしい

React Nativeの画像キャッシュ

プロローグ

React Native使ってみた。Imageコンポーネント、毎回ネットワークから取得してるじゃん、これキャッシュしてくれないの?と思った。

調べた

[0.32] Image caching broken · Issue #9581 · facebook/react-native · GitHub

なんか壊れてんヨーという報告を発見。やっぱ壊れてんのかと思いつつ。 でもキャッシュしてもらえる人ともらえない人がいるみたい。日頃のおこない? ちなみに俺はキャッシュしてもらえてない。バージョンは0.38.0で。

なんでやねんと色々思っていると、画像サイズが大きいとダメだというコメントが目に入る。 画像サイズを小さくして試してみる。いけた。600KB -> 178kBぐらいに絞った。

どうやらReact Nativeは画像の取得にiOSに元からあるNSURLRequestという仕組み使っていて、そのNSURLRequestNSURLCacheさらにはNSURLSessionとやらの仕様にしたがってキャッシュを行うらしい。

そしてこのNSURLSessionは指定した上限の、5%までのファイルサイズしかキャッシュしないみたい。

で、React Nativeはどれほど指定しているのかなーと思ったら

  _decodedImageCache.totalCostLimit = 5 * 1024 * 1024; // 5MB

なるほど600KBはキャッシュされないですねと。てか上限小さすぎない?そしてそもそも

static const NSUInteger RCTMaxCachableDecodedImageSizeInBytes = 1048576; // 1MB

1MB以上のファイルはRN側でキャッシュしないように指定してるみたい。参照したのはここらへんhttps://github.com/facebook/react-native/blob/v0.38.0/Libraries/Image/RCTImageCache.m#L23

まぁただここらへんの挙動は変数名から推測した程度なので、自信はない。

TL;DR

画像サイズが大きいとダメみたい。250KB以下にした方が良さげ。 Imageコンポーネント、キャッシュしてくれる。シミュレーターでも動作が怪しいかもしれない。実機で動かしたらちゃんとキャッシュしてくれてた。

その他

[追記: 2017/3/30]

書き忘れていたけれど、画像をuriで指定して得るレスポンスのメタデータにcache-contolを含めていないとキャッシュしてくれない。 S3とかに上げるときは設定を確かめるのを忘れずに。

[追記: 2017/4/19]

Facebook内部ではオープンになってるコンポーネントとは別のImage, Cachingモジュールを使ってるみたい。 このIssueが閉じるまでは、Imageコンポーネントのキャッシュは安定しなさそう。自分の場合、3回ぐらいアプリ開くとキャッシュが消されるという挙動をしている。(0.43.3)

https://github.com/facebook/react-native/issues/9581#issuecomment-287834859

参考リンク

[0.32] Image caching broken · Issue #9581 · facebook/react-native · GitHub

Change RCTImageLoader's Cache System to default NSURLRequest's cache … · facebook/react-native@631785f · GitHub

https://github.com/facebook/react-native/blob/v0.38.0/Libraries/Image/RCTImageCache.m

URLSession:dataTask:willCacheResponse:completionHandler: - NSURLSessionDataDelegate | Apple Developer Documentation

ios - How to cache using NSURLSession and NSURLCache. Not working - Stack Overflow

浅草寺の思い出

数年前に浅草寺に行った。時期は忘れた。おみくじを引くところに年季の入ったお姉さん方が群がり、何やら盛り上がっていた。一人一人何が出たかを読み上げている。わたし小吉、わたしは吉。そして恋愛運がなんたらだとか。よくある光景だ。特に気にもとめず、そのままお堂の外に出ようとした。ところがそのとき、お姉さん方のひとりが放ったひとことに一瞬足を引き止められた。

「わたしはね、えーっと、『世界を統治する』って書いてある!何これー!」

ナニモンだよ、と。一人だけ恋愛だとか結婚だとかいう話題じゃなくて、何かとんでもないものを引き当てている。

その後が気になったけど、そのまま外に出ちゃったからあとのことは知らない。けれどきっと、世界は今その人のおかげで回っているんだと思う。ありがたや。まぁそんなことより、その人にとっては婚期の方が大事だったと思うけれど。

歓談はそして、立ち込める線香の向こうに消えていった。