production.log

株式会社リブセンスでエンジニアをやっている星直史のブログです。

Expoアプリで使うWebViewはreact-native-communityを使わなければ表示させることができない

概要

ExpoアプリでWebViewを使用する際、以下のソースコードでは、画面に文字列を出力することができません。

import React, { Component } from 'react';

import { WebView } from 'react-native';

export default class Hoge extends Component {
  render() {
    return (
      <WebView
        source={{
          html:"<html><body>Hello<br/>World!</body></html>"
        }}
      />
    );
  }
}

今回はExpoアプリで使うWebViewはreact-native-communityを使わなければ表示させることができない事について書きます。

原因

原因についてはReact Nativeの公式ドキュメントにデカデカと警告で表示されていますね。

reactnative.dev

Warning Please use the react-native-community/react-native-webview fork of this component instead. To reduce the surface area of React Native, is going to be removed from the React Native core. For more information, please read The Slimmening proposal.

一応日本語で書きますと
「警告: 代わりに、このコンポーネントのreact-native-community/react-native-webviewを使用してください。 React Nativeのサイズを減らすために、WebViewはReact Nativeコアから削除されます。詳細については、スリム化の提案をご覧ください。」

一方、ExpoのWebViewのドキュメントをみてみましょう。

docs.expo.io

react-native-webview provides a WebView component that renders web content in a native view.

と記述があります。
自分がハマっていたのはまさにここでした。
私はExpo v33.0.0からReact Nativeを触り始めていたのですが、当時はReact Nativeの公式ドキュメントを参考に、WebViewをReact Nativeからimportして使っていました。
しかし、Expoにおいては、後にReact Nativeコアから独立して作れるreact-native-community/react-native-webviewを使わなければ、Expoアプリで表示できなかったのでした。

解決方法

Expoの公式ドキュメントにある通りです。

Installation

プロジェクトのルートで、react-native-community/react-native-webviewをインストールします。

$ expo install react-native-webview

コードの修正

コードの修正は、import文をreact-nativeからreact-native-communityに変更するだけです。

import React, { Component } from 'react';

- import { WebView } from 'react-native';
+ import { WebView } from 'react-native-webview';

export default class Hoge extends Component {
  render() {
    return (
      <WebView
        source={{
          html:"<html><body>Hello<br/>World!</body></html>"
        }}
      />
    );
  }
}

RailsでCSVを生成し、Excelで開く時にハマったこと

概要

久しぶりのRailsネタ。
管理画面的なシステムで、RailsからCSVを出力し、それをエンドユーザーがExcelで開くようなシュチュエーションで困ったことがありました。

  • セル内の日本語が文字化けしてしまう
  • 0埋め文字を生成しても、Excelで先頭の0が除かれてしまう

それぞれ5分ほどハマったのと、複数回ハマりそうなのでRailsでCSVを生成し、Excelで開く時にハマったこととしてメモに残します。

セル内の日本語が文字化けしてしまう

手法は2つです。

  1. BOM付き(UTF-8)でCSVを出力する
  2. SJISにエンコードして出力する

BOM付き(UTF-8)でCSVを出力する

bom = "\uFEFF"
header = %w(ID 名前)
CSV.generate(bom) do |csv|
  csv << header
end

generateメソッドの引数にbomを渡すわけですね。

SJISにエンコードして出力する

CSV.generate(encoding: Encoding::SJIS, row_sep: "\r\n", force_quotes: true) do |csv|
   ...
end

先ほどと同様に、generateメソッドの引数にエンコードを指定する方法です。

0埋め文字を生成しても、Excelで先頭の0が除かれてしまう

ExcelでCSVを読み込む際、"001"という文字列を数値として扱ってしまいます。
これを避けるために、「"001"は文字列ですよ」とExcelが認識できる形で出力します。

Excel単体で考えた場合に、セル内に「="001"」と入力すると文字列として認識してくれるため、
CSV出力時もこれと同様の文字列を作ると、うまく行きます。

CSV.generate(bom) do |csv|
  csv << [format('="%04d"', "0001")]
end

まとめ

いつもRailsでCSVを出力してExcelで確認してもらう時に、「オレは何と戦っているんだ....」という気持ちになりますが、そんな気持ちになる前に瞬殺してしまえば良いと思い立ち書きました。

  • セル内の日本語が文字化けしてしまう
  • 0埋め文字を生成しても、Excelで先頭の0が除かれてしまう

React Native Debuggerの長時間使用時のメモリリークを防ぐ方法

概要

React Nativeを使用したアプリの開発時に必要になるものはデバッガーです。デバッガーにはいくつか種類があります。

  • Chrome
  • VSCode
  • Reactotron
  • React Native Debugger
  • console.log*1

これらの中で、私はReact Native Debuggerを使用しています。

  • Chrome: debugger-uiページをアクティブにしていないと、エミュレーターにWarningが出てしまうのが気に入らない
  • VSCode: Vimを使っているため除外
  • Reactotron: 設定ファイルを書くのに気乗りしなかった

当時(2019年初頭)、上記のような理由(消去法)でReact Native Debuggerに決まりました。
React Native Debuggerは導入も機能も充実しているのですが、唯一の欠点は長時間使っているとメモリリークしてしまうことです。
平気で5GB以上食い潰すので、スワップ使用領域が増えてPCが重くなり、気づくこともあります。
今回はこのReact Native Debuggerの長時間使用時のメモリリークを防ぐ方法について書きます。

設定ファイルを変更

これまで、いちいち再起動していたのですが、アホらしくなったので、設定ファイル的なものがないかを調べたところ、ありました。

github.com

これらの設定の中でtimesJSLoadToRefreshDevToolsがどうやらそれっぽいです。

// Refresh devtools when doing JS reload every N times. (-1 for disabled)
// This can effectively avoid possible memory leaks (Like
// https://github.com/jhen0409/react-native-debugger/issues/405) in devtools.
timesJSLoadToRefreshDevTools: -1,

とあるので、n回ごとにJSリロードを行うときにdevtoolsを更新するようです。
デフォルトの設定では-1であるため、無効となっています。
コメントにも「これにより、メモリリークの可能性を効果的に回避できます 」とあるので、一応作者が推している方法のようですね(白目)

設定は以下の通り、行います。
メニューバー => Debugger => Open Config File
すると、エディタが開くので、timesJSLoadToRefreshDevTools: 5といった感じで、-1以外の数値を設定します。
ちなみに、リフレッシュする際はほぼシームレスに行われるため、待ち時間はそこまでありません。私は5回に設定しました。

まとめ

React Native Debuggerの唯一にして最大の欠点であるメモリリーク問題ですが、設定ファイルを変更することで解決しました。
解決方法として、作者がプロダクトのメモリリークを根本解決するものではなく、n回JSのリロードしたらリフレッシュするといったものでしたが、
なにはともあれ解決できたのでよかったです。

*1:流石にこれだけだとキツいですが

AWS EC2上でReact Native + Expo アプリを動かし、手元のスマホに配信する方法

概要

React Native + Expoを使用すると、エミュレータだけではなく、実機でも手軽にアプリの動作を確認することができます。
ただし、制約もあります。

  • 実機への配信は、同一のネットワークからでなければ配信することができない
  • 配信元はローカルのPCとなるので、マシンスペックが高くないと作業効率が落ちる*1

ウチの会社では3月末まで自宅勤務推奨期間で、ほとんどのメンバーが自宅からリモートで業務を行っています。
このような状況から、同一のネットワーク、ローカルPCから配信することは現実的ではなくなったため、さくっとクラウド内だったり、SaaSを使って痛みから開放されないか模索しました。

今回は模索した先にたどり着いたAWS EC2上でReact Native + Expo アプリを動かし、手元のスマホに配信する方法について紹介します。

候補

候補になったのは複数あります。

  • AWS EC2から配信
  • Appetize
  • Genymotion Cloud ...

などなど、候補はいくつかありますが、安価で手っ取り早く解決できそうだったAWS EC2にしました。
(初手で解決できてしまったのもあり...、しっかり比較検討できませんでした)

手順

EC2の設定

まずはEC2を起動します。
ExpoでJavaScriptビルドが複数走るので、インスタンスタイプはC系が良いでしょう。

nodeのインストール

expo-cliを使用するために、nodeをインストールします。

$ curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.32.0/install.sh | bash
$ . ~/.nvm/nvm.sh
$ nvm install stable
$ node -v

続いて、ExpoのプロジェクトでYarnを使用している(いた)ので、それもインストールします。

$ npm install -g yarn
$ yarn install

最後に、expoコマンドを使用するためにexpo-cliをインストールします。

$ npm install --global expo-cli

EC2からの配信確認

EC2から配信するためには、2つやることがあります。

  • 配信方法にtunnelを指定する
  • バックグラウンド起動しておく(nohup ~~~ &)

配信方法にtunnelを指定すると、Expoがインターネットを経由してビルドしたファイルを配信します。
また、バックグラウンド起動については、単純にexpo startとしてしまうと、フォアグラウンドで実行されてしまうため、ssh接続を終えてしまうと、プロセスも切れてしまいます。 それを回避するためにバックグラウンド起動をします。
コマンドは以下の通りです。

$ nohup expo start --tunnel &

最後に、デバイスからEC2上で起動したExpoのQRコードを読み取ります。
QRコードはnohup.outというファイルに出力されます。

$ cat nohup.out

f:id:watasihasitujidesu:20200310195604p:plain

まとめ

単純にEC2にExpoをインストールし、配信方法をインターネット経由のtunnelに指定することで、手軽にインターネットの向こう側の人に配信することができました。

*1:CPU 2.9GHz 6コアIntel Core i9 / メモリ32GB 2400MHzをもってしても、3つ並行して動かされるとキツいです

Re:スムーズに技術書を入稿するためのチェックポイントと値付けについて

技術書典8は新型コロナウィルスにより中止になりましたが、印刷所への入稿が割引率が最も高い早割で申し込むことはできました。
技術書典の参加は今回で2回目でしたが、1回目で覚えた内容をほぼ忘れていたため、
前回メモ書き程度に残した自分のブログを頼りに一から調べました。*1
今回は、前回のメモ以外に調べたこと + 印刷所から入稿時の指摘など、スムーズに技術書を入稿するためのチェックポイントと値付けについてを書きます。

参考にしたサイト

基本的には、自分ので書いたブログを読み返しました。

blog.naoshihoshi.com

それでも足りなかった点は以下の通りです。

Re:View自体の記憶を呼び起こす

PCを変えたのでRe:Viewのインストールからはじめました。 qiita.com

次はRe:Viewの記法ですね。記法は覚えるモチベーションが湧かなかったため、チートシートを逐一確認しながら書きました。
Re:VIEWチートシート · GitHub

PDF出力の記憶を呼び起こす

次に記憶を失ったポイントは、入稿のためのPDF出力です。 自分のブログでもPDF出力はざっくり書きすぎていたので、別の記事を参考にしました。 PDF出力は、この記事を参考にしただけで事足りました。

dev.classmethod.jp

印刷所の担当に方から指摘された記憶を呼び起こす

前回の指摘は以下の通りです。

  1. 出力された用紙サイズをB5にする
  2. 通しのページ番号(ノンブルという)をいれる
  3. ページが偶数、または4の倍数にする
  4. 読み込めていないフォントがあるか確認する
  5. 表紙と本文は分けてzipにする
  6. 表紙はPSDで入稿し、解像度は350 or 600とする <== NEW!!

このうち、1~3の詳細はサークル仲間の@kaibaさんの記事が参考になります。
https://pokosho.com/b/archives/3239

なかでも、通しのページ番号(ノンブルという)については、以下の記事通りにやれば問題ないはずです。 qiita.com

あと、今回ハッとしたことは、ページ数は表紙 + 本文で決まります。 また、表紙は表裏表紙があるので、4ページ必要です。そのため、本文が100ページの場合、104ページが入稿時のページ数になります。

今回の指摘点

今回は、表紙についてミスがありました。

  • 解像度を350 or 600ではなかった(70とかだった)
  • PSDではなく、PNG形式で入稿してしまった

解像度については、maxOS標準のプレビューで変更が可能です。 PNGからPSDへの変換はオンラインサービスを利用しました。

convertio.co

価格

自分の場合は、在庫を抱えたくなかったので、40部に抑え、ダウンロードカードも準備して機会損失にも対応できるようにしました。 ただ、次回は、日光企画さんの早割(50%)で入稿して、100部くらい刷ろうと思います

前回は、物理本は45部程度売れました。
ただ、Tryとして100部刷ろうとしていたようです。*2

100部は流石にやりすぎでは?!と思ったため、日光企画さんのオンデマンド平トジフルカラーセットの価格表を確認します。

日光企画さんはページ数と部数で価格が決まります。 www.nikko-pc.com

こんな感じです。
f:id:watasihasitujidesu:20200217221550p:plain

そして、この価格をよく見ると、価格が歪んでいることに気付きました。 ページ数と部数に応じて価格が線形になっているのかと思いきや、そうではありませんでした。

では、上記の表を1冊あたりの価格を表示したものにしてみます(合計金額 / 部数)
f:id:watasihasitujidesu:20200217221928p:plain

例えば、28ページで10部すると単価が 906円ですが、15部にすると776円になります。
しかし、20部にすると705円になります。それほど価格が下がりませんでした。
では、一体何部刷るのが良いのでしょう?最もコスパが良い部数を特定するため、部数間の差を調べてみます。
(先の例だと、15部は10部と比べて130円、20部は15部と比べて71円安い...というように)

f:id:watasihasitujidesu:20200217222429p:plain
日光企画さんは10部~300部まで刷れます。 10部から100部までは部数が多くなるごとに値引率は低くなりますが、100部と150部の差が明らかに大きいです。 もちろん、刷れば刷るほど、金額が大きくなりますし、在庫を抱えては赤字になることはたしかです。
余った本は、とらのあなさんだったりBOOTHで販売するんや!!という感じだったり、100部か150部か迷う...という感じの方の参考になればと思います。

まとめ

2年前に参加した技術書典以来、触っていなかったRe:Viewですが、2回目だったので少しサイトを漁ると、記憶が蘇ってきたので、見るべきサイトは結構少ない印象でした。
とはいえ、印刷所から指摘はあったので、今回もまとめました。
あとは、価格ですね。今回はえいやっ!で決めずに、日光企画さんの価格表とにらめっこして、コスパの良い部数が特定できたのは良かったです。

f:id:watasihasitujidesu:20200217223526j:plain

*1:途中で記憶が蘇ってきました

*2:おそらく気分がHIGHになっていたのでしょう

ReactNativeでカメラロールから写真を複数枚選択する処理の実装

概要

自分が作っているプロダクトであるSnapmartは、スマホのアプリから誰でも手軽に写真を売り買いできるサービスです。
スマホアプリでは、カメラロールの中から写真をアップロードをする機能があります。
この写真をアップロードする機能ですが、カメラロールから写真を複数枚選択する処理の実装に非常に苦労しました。
この記事では、ReactNativeでカメラロールから写真を複数枚選択する処理をどのように実装しているのか / 実装の前に何を比較検討したのかを紹介します。

仕様の説明

まずSnapmartアプリにおける、写真の選択画面の仕様について、説明をします。
大まかな流れは以下の通りです。

  1. アップロードしたい写真があるアルバムを選択
  2. アルバムの中からアップロードしたい写真を複数選択

アルバム選択画面

f:id:watasihasitujidesu:20200213184903j:plain

こちらのアルバム選択画面の仕様は、以下の通りです。

  1. BottomNavigationの出品アイコンをタップするとスクリーンの表示
  2. カメラロール内の以下の項目を取得し表示(iOSの場合)
  3. 最近の項目
  4. お気に入り
  5. 存在するアルバム全て

写真選択画面

f:id:watasihasitujidesu:20200213184918j:plain

こちらの写真選択画面は以下の通りです。

  1. アルバム選択画面で選択したアルバム内の全ての写真を表示
  2. 表示される写真は撮影日の降順
  3. 複数枚選択し、アップロード

仕様だけなら非常にシンプルですね。

試してみたこと & 問題点

先述の仕様を実装するために、以下のことを試しました(実際にリリースもしました)。

  • アルバム選択画面を実装せず、直近1万枚の写真を表示し選択してもらう画面のみ実装
  • アルバム選択画面を実装せず、直近10万枚の写真を表示し選択してもらう画面のみ実装
  • ImagePickerを使用する

アルバム選択画面を実装せず、直近1万枚の写真を表示し選択してもらう画面のみ実装

元々のアプリはSwiftで実装されていたのですが、これをReactNativeに刷新しました。

blog.naoshihoshi.com

当初いち早くリリースするために、軽微(だと錯覚していた)機能は実装せずにリリースしてしまいました。
既存のユーザーさんからすると以下のような状態になってしまいました。

  • SwiftだろうがReactNativeだろうが、見た目は変わらない
  • それなのに、これまで使っていた機能が無くなってしまい不便になった

また、直近1万枚の写真しか表示されないので、1万件を超える写真が端末に保存されていた場合に画面に表示されない問題も発生しました。 これはテスト端末機に写真が1万枚も保存されていなかったため、事前に見抜くことができませんでした。

その結果、お問い合わせが急増してしまったり、アップロード率が低下してしまいました。

アルバム選択画面を実装せず、直近10万枚の写真を表示し選択してもらう画面のみ実装

お問い合わせが急増してしまったので、急いで修正する必要があります。 まず、1万枚の制限を10万枚に緩和してリリースしました。

しかし、10万枚を表示させたことによって、10万件の写真のソート処理に時間がかかってしまう問題に引っかかってしまいました。
基本的には、React NativeのCameraRoll APIを使用して実装をしています。 ところが、CameraRoll APIには、写真の並び順を指定するオプションが存在しないため、自前で実装する必要があります。 以下のようなイメージで実装します。

CameraRoll.getPhotos(options)
  .then((obj) => {
    let images = [];
    obj.edges.forEach(asset => {                                                                                 
      images.push(asset.node);
    })
    images.sort(function(a, b) {                                                      
      return b.timestamp - a.timestamp;
    })
  })

最大10万ループするので、写真を多く持つユーザーさんほど処理が遅くなってしまいます。
こちらでリリースした結果、以下のような問題点がありました。

  • ソートしている間は画面が真っ白になる
  • ソートにリソースが持っていかれるため
    • アプリがクラッシュしてしまう
    • 充電の減りが早い

ImagePickerを使用する

自前で実装しない方法を模索していたときに発見したのが、Expoが提供するImagePickerです。

ImagePicker - Expo Documentation

上記のページにある通り、このように実装するだけで、カメラロールからの写真選択が実装できます。
非常に素晴らしいAPIです。

ImagePicker.launchImageLibraryAsync

ただ、このAPIは写真を一枚ずつ選択しなければならないため、Snapmartにおける複数枚アップロードしたいという要望は満たすことができません。 試しにImageaPickerバージョンで社内メンバーに触ってもらったところ、見事にお蔵入りになりました。

悩んだ末の実装

結局、CameraRollで処理を頑張ることにしました。
また、CameraRoll.getPhotosにはオプションでgroupNameを指定することができます。
このgroupNameは写真が属しているアルバム名です。
そのため、Snapmartアプリにアルバム機能を実装して、CameraRoll.getPhotosに渡すオプションで、選択されたアルバム名をgroupNameで渡すことで、 ソートする写真を減らすというアプローチにしました。

ただ、アルバム名を取得しようにも、カメラロールに保存されている最後の1枚までアルバム名を取得しなければならず、結局ループする回数は減らすことはできませんでした。
苦肉の策として、以下を実装しています。

  • 一度取得したアルバム名(groupName)はAsyncStorageに格納する
  • AsyncStorageにデータがあった場合はそれを表示
  • アルバムを作成/削除した場合のために再読み込みボタンを設置
let folderNames = [];
CameraRoll.getPhotos(options)
  .then((obj) => {
    obj.edges.forEach(asset => {
      if(!folderNames.includes(asset.node.group_name)){
        folderNames.push(asset.node.group_name);
      }
    })
    AsyncStorage.setItem("hogehoge", folderNames.join(","));
  })

また、アルバム名(asset.node.group_name)で絞り込む場合はCameraRoll.getPhotosのオプションで指定します。

const options = {first: 99999, assetType: "Photos", groupTypes: "Album", groupName: "アルバム名"}
CameraRoll.getPhotos(options)

ReactNativeでカメラロールから写真を複数枚選択する場合のベストエフォート

  • カメラロールから写真を複数枚選択する
  • 写真の並び順を変更する

という要件は、簡単に実装できるように見えて、ReactNativeにおいては自前で実装が必要です。 また、CameraRoll APIは写真の並び順の指定ができないため、全ての写真をループで回し、timestampの降順/昇順、ファイル名の昇順/降順を決定する必要があります。

この問題点を少しでも緩和するために、写真選択画面の前に、アルバム選択画面を置くのが、今のところ最善策のように思えます。 理由は、アルバム選択をしてもらった場合、写真のループ処理の母集団が少なくなる(=それだけ処理が早く終わる)ためです。

rake db:migrateでIndex column size too large.と言われてしまった場合の対応

概要

development環境にMySQLを構築して一からmigrationを実行しようとした時に、Index column size too large.と言われてしまいました。
この記事ではrake db:migrateでIndex column size too large.と言われてしまった場合の対応についてメモ程度にまとめます。

原因

原因は以下の通りです。

  • MySQLだとキープレフィックスが767バイト制限がある
  • Rails(ActiveRecord)をutf8mb4で動かしたい
  • string型のカラムを定義するとvarchar(255)になり、utf8mb4でインデックスを張ると767バイトを超えてしまう

対応

対応は大きく2つあります。

  1. my.cnfによるMySQLの設定変更
  2. テーブルのrow_format変更

my.cnfによるMySQLの設定変更

my.cnfの設定

まずは、my.cnfの場所を確認します。

$ mysql --help | grep my.cnf
                order of preference, my.cnf, $MYSQL_TCP_PORT,
/etc/my.cnf /etc/mysql/my.cnf /usr/local/etc/my.cnf ~/.my.cnf 

macOSでbrewを使ってMySQLをinstallしているとコイツもいたりするので、注意が必要です。

/usr/local/Cellar/mysql\@5.6/5.6.42/my.cnf

my.cnfが特定できたら以下の設定を追加します。

[mysqld]
innodb_file_format_max = Barracuda
innodb_file_format     = Barracuda
innodb_file_per_table = 1
innodb_large_prefix   = 1

innodb_large_prefixオプションはインデックスの767バイト制限を3072バイトまで拡張するオプションです。 このオプションを適用させるには、テーブルのrow_formatをDYNAMICもしくは、COMPRESSEDにする必要があります。

この変更はALTER TABLEを使うか、ActiveRecordのcreate_tableメソッドを書き換えてデフォルトの挙動を変更する方法の2パターンがあります。(後述します)

MySQLの再起動と設定反映の確認

my.cnfの設定を変更したらMySQLの再起動をしなければ設定が読み込まれません。

brew services restart mysql

その後、mysqlに接続し、以下の結果が得られればうまく設定されています。

mysql> show global variables like "innodb_file%";
+--------------------------+-----------+
| Variable_name            | Value     |
+--------------------------+-----------+
| innodb_file_format       | Barracuda |
| innodb_file_format_check | ON        |
| innodb_file_format_max   | Barracuda |
| innodb_file_per_table    | ON        |
+--------------------------+-----------+
mysql> show global variables like "innodb_large_prefix";
+---------------------+-------+
| Variable_name       | Value |
+---------------------+-------+
| innodb_large_prefix | ON    |
+---------------------+-------+
1 row in set (0.01 sec)

テーブルのrow_format変更

次にテーブルがのrow_formatを変更する必要があります。 このrow_formatの変更は2つの方法があります。

  1. ALTER TABLEによる変更
  2. ActiveRecordのcreate_tableメソッドのデフォルトのrow_formatをDYNAMICに変更

それぞれ説明します。

ALTER TABLEによる変更

まずは現在の状態を確認します。 (この記事にたどり着いたということは、おそらく対象のテーブルはCompactになっているはずです。)

mysql> select row_format from information_schema.tables where table_schema="DB名";
+------------+
| row_format |
+------------+
| Compact    |
| Compact    |
| Compact    |
...

次に、対象のテーブル(エラーが出てしまったテーブル)のrow_formatを次のクエリで変更します。 ALTER TABLE テーブル名 ROW_FORMAT=DYNAMIC;

最後に期待の結果になっているか確認します。

mysql> select row_format from information_schema.tables where table_schema="DB名";
+------------+
| row_format |
+------------+
| Compact    |
| DYNAMIC   |
| Compact    |
...

ActiveRecordのcreate_tableメソッドのデフォルトのrow_formatをDYNAMICに変更

先人がいましたので、説明はそちらに譲ります!

qiita.com

最後にbundle exec rake db:migrateを実行し、うまくいくかを確認します。

まとめ

今回はrake db:migrateIndex column size too large.が出てしまう問題(通称767バイト問題)の解決方法を紹介しました。 これは、MySQLのmy.cnfの設定の変更と、テーブルのrow_formatの変更が必要です。 テーブルのrow_formatALTER TABLEで直接修正する方法もありますし、ActiveRecordのデフォルトの挙動を変更して対応することも可能です。

ReactNative製AndroidアプリからさらにiOSアプリ実装が終わったのでKPTを行う

この記事はReact Native アドベントカレンダーの23 日目の記事です。

概要

SnapmartのAndroidアプリはReact Nativeで実装されています。
iOSアプリはSwiftで実装されているのですが、この度、React Nativeで置き換えることにしました。

手順は、まずAndroidアプリを作り、それからiOSアプリを置き換えることにしました。*1
今回は、ReactNative製AndroidアプリからさらにiOSアプリ実装が終わったので、そのKPTを行います。

Keep

動作しない部分の洗い出しと、修正が迅速に行えた

iOSのエミュレーターで動作させたときに、すぐに動いた訳ではないのですが、下記切り分けがスムーズだったので、滞りなく修正ができました。

  • AndroidOnlyのオプション使用
  • React Nativeが提供するコンポーネントのOS別の差分

Android Onlyのオプション使用

こちらは単純にAndroidしか動作しないオプションを使用していたため、iOSでは動作しないという問題でした。
今回頻繁した具体的な問題は、Text Input コンポーネントの複数行入力で使用するnumberOfLinesオプションです。 当初何の気なしにAndroid Onlyオプションを使っていた自分を呪いました。 OSによって実装を分けることはクロスプラットフォーム開発の利点を殺しかねないので、用法用量は適切に!

React Nativeが提供するコンポーネントのOS別の差分

動作確認を行なっていると、しばしば「Androidと微妙に挙動が違うよね?」というコンポーネントがありました。 最も差分があったのはPickerでした。

Androidでは、Pickerと選択後の値表示が一つのコンポーネントで完結するのですが、iOSでは、Pickerは単純に選択肢を表示するだけのコンポーネントでした。
そのため、iOSでは別途Textコンポーネントを用意し、onPressでPickerを発火させるといったことをしなければなりません。
問題の解は見えているのですが、腑に落ちない*2ため、react-native-picker-selectを使用することにしました。
(その他、小さい問題は、Textコンポーネントのstyleとして、borderColorが動作しないというものもありました。)

ほとんどが、上記2パターンで切り分けできたので、

  1. 不具合報告がある
  2. コンポーネントのオプションを調べる
  3. 2で問題ない場合、OS別の差分であると断定し、修正

という感じで、サクサク修正することができました。

Problem

動作確認の負担が重い

Snapmartアプリは比較的小規模なアプリであるとはいえ、手動テスト項目は250件にもなります。 これらをデバイス別(iPhone 6, 8, X)で行うため750件になります。 通常業務も行いながら、テストを実施をしているため、全て完了するのに、おおよそ1週間ほど必要になります。

負担は非常に大きいです。

Appleの審査になかなか通らなかった

いざ、Appleへ審査申請をしましたが、7度却下されました。 具体的な却下理由は下記の3点です。

  1. メタデータ(App Storeで使用する写真の解像度)が不適切(ガイドラインに沿っていない)
  2. ログインできない
  3. ボタンが動作しない(!)

1,2はしょうもないミスだったのですが、3の指摘で5度の却下と1ヵ月前後レビュアーさんとのやりとりや修正作業を行いました。

ボタンが動作しない指摘の修正

Appleの審査はiPadで行われ、*3最新のOS(この記事執筆時点では12.4)で動作させているようです。
また、最新のエミュレーターではOSが12.2までしか上げられませんでした。
さらには、Expo Clientアプリを経由したアプリの動作と、ビルドした.ipaファイルを実機にインストールしたものでは、若干挙動が異なりました。

そのため下記差分が重なり、不具合を特定するのに時間がかかってしまいました。

  • iPhoneとiPadの筐体の違い
  • OSのパッチバージョンの差分
  • .ipaファイルでの動作確認不足

また、これらは一見落ち着いて切り分けできているように見えますが、「Appleのレビュー担当者のミスなのでは?」「なぜiPadを使っているのか?」「もう一度ビルドしたら挙動が変わるかも?」といった、問題を直視せず、憶測で修正するといった愚行を重ねてしまいました。
まず、他人(Appleのレビューの方)が行なってくれたことは何一つ間違っていなかったことと、親切に受け答えしてくれたので、「自分はなんてことをしてしまったんだ...」という思いになりました。
それと同時に、未知の物に対する不安は判断精度を低くしてしまうことも自分なりの発見でした。

事実を基に落ち着いて判断。問題解決の鉄則ですね。

Try

Detoxを導入

今回のiOS実装だけではなく、Expo SDKのバージョンアップでも全体的な動作確認は行います。また、Expoのバージョンアップの頻度は非常に短いです。
規模が大き目のリリースのたびに、動作確認をメンバーにお願いするのは負担、効率、スピードなどを考慮すると、極力避けたいです。
また、動作確認に関しては、E2Eテストである程度自動化できます。
それを実現するのがDetoxです。

github.com

アプリの様々な機能のうち、主要機能から攻めていきたいと思います。

まとめ

React NativeでAndroidアプリを作成したのちに、iOSのReact Nativeチャレンジしましたが、Androidと同様の仕様で挙動させること自体は簡単でした。
自分の場合、初のApple審査申請で日和ってしまい、落ち着いて的確な判断ができなかったことが、遅延した原因です。
また、リリースサイクルの中で手動によるボトルネックが問題として認識できました。

*1:当初そもそもAndroidアプリがなかったため。

*2:OS別の処理は極力書きたくない

*3:iPadで提供する/しないに関わらず

AWS S3で特定のバケット内のオブジェクトをワイルドカードを使用してい削除するコマンド

概要

AWS S3を利用していると、ある特定のバケット内のオブジェクトをワイルドカードを使って削除したい場面があると思います。 シェルスクリプトのrmコマンドと同じノリでいけるかと思いきや、オプションを指定しなければならなかったので、そのメモを残します。

コマンド

操作はAWS CLI を使って行います。

使用するオプション

  • --exclude: 除外したいワード
  • --include: 含めたいワード
  • --recursive: 指定したバケット内を再帰的に削除
  • --dryrun: テスト実行

バケットを特定している場合

aws s3 rm s3://hoge/ --exclude "*" --include '*2019*' --recursive --dryrun

一気に掃除したい場合

aws s3 ls s3:// | awk  '{ print "aws s3 rm s3://"$2" --include \"*2019*\" --recursive --dryrun"}' |sh

React Native + ExpoによるiOSアプリ審査申請時の広告ID(IDFA)設定

概要

iOSアプリの審査申請時、広告ID(IDFA)を選択するように言われます。
間違った選択をすると審査で落とされてしまうのですが、毎度毎度「どの設定したらいいんだっけ?」と悩まされます。
今回は自分用にReact Native + ExpoによるiOSアプリ審査申請時の広告ID(IDFA)設定についてメモを残そうと思います。

設定項目

設定項目は2点です。

  1. 広告ID(Advertising identifier又はIDFA)の設定
  2. 広告IDを使用する場合の目的

広告ID(Advertising identifier又はIDFA)の設定

広告IDとは、デバイスに付与される固有の識別子です。
広告配信者は、この広告IDを使用しデバイスを特定することで有益な情報を消費者に提供しよう!というものです。 そして、iOS端末の広告IDをIDFAと呼びます。

React Native + Expoを使用している場合は「はい」を選択してください。

実装時に広告を入れていなかったとしてもExpoが裏側で広告IDを使用するため(Segment Analyticsに依存しているため)「はい」を選択する必要があります。

f:id:watasihasitujidesu:20191126200656p:plain

広告IDを使用する場合の目的

次に、目的を選択しなければなりません。 ここで選択するものは3つです。

  • この App のインストールを前に提供した広告と関連づける
  • この App 内で行われたアクションを前に提供した広告と関連付ける
  • iOSの「追跡型広告を制限」設定

segment.com

f:id:watasihasitujidesu:20191126201154p:plain

以上の選択で、審査時に広告ID設定の不備で却下されることは無くなります。

Apache Bench(abコマンド)をmacOSで実行した場合に出るエラー「socket: Too many open files (24)」の回避方法

概要

AWS ELBの暖気申請(Pre-Warming)をしたかったのですが、サポートのプランがBusiness以上でなければ申請できないため、自前でPre-Warmingをする必要がありました。 AWS ELBは負荷が上がった場合に、AutoScaleするLBです。
通常、事前に大量のトラフィックが予想できる場合は、WebサーバーとDBはスケールアップ/アウトできるのですが、その前段のLBが捌き切れないことが多々有ります。 マネージドサービスの場合、スケールするまで、数分~数十分要する為、機会損失が多くなってしまいます。

そこで、ELBの特性を考えて、自前に意図的に暖気(AutoScaleさせる)ことにしました。 負荷テストなどでよく使われるApache Benchを使おうとしたのですが、やっている途中でエラーが発生してしまったので、その備忘録としてApache Bench(abコマンド)をmacOSで実行した場合に出るエラー「socket: Too many open files (24)」の回避方法について書きます。

遭遇したエラー

Apache Benchの使用方法は下記の通りです。

ab -n [総リクエスト数] -c [同時リクエスト数] [URL]

[同時リクエスト]で指定した数値で[URL]に対してリクエストを行います。 リクエスト数が[総リクエスト数]に達したら終了です。

負荷をかけたいので、同時リクエスト数を増やすことになると思いますが、この数値を大きくした場合にsocket: Too many open files (24)というエラーが発生します。

原因

socket: Too many open files (24)は、プロセスが開けるファイルディスクリプタの上限に達してしまうと発生するエラーです。 そのため、この上限を変更する必要があります。

上限の確認は下記コマンドで確認ができます。

$ ulimit -n
 256

ulimitは使用できるリソースを制限するコマンドです。ファイルの最大サイズ、使用可能メモリ、同時実行プロセス数などを確認&制限できます。 -nオプションは、同時に開けるファイル数です。

回避方法

回避方法は2つあります。

一時的に設定を変更する

先述したulimitコマンドで設定を変更します。

$ ulimit -n
256
$ ulimit -n 1024
$ ulimit -n
1024

この変更はターミナルを閉じてしまうと既定値に戻ります。 そのため、ulimitを使用する場合は、変更した直後(ターミナルのプロセスを閉じないうち)にApache Benchを実行する必要があります。

恒久的に設定を変更する

恒久的に設定を変更したい場合はlaunchctlコマンドを使用します。 このコマンドはmacOSで使用できるコマンドで、macOSのプロセスを管理するためのコマンドです。

現状を確認するには下記コマンドを実行します。

$ sudo launchctl limit
    cpu         unlimited      unlimited      
    filesize    unlimited      unlimited      
    data        unlimited      unlimited      
    stack       8388608        67104768       
    core        0              unlimited      
    rss         unlimited      unlimited      
    memlock     unlimited      unlimited      
    maxproc     2837           4256           
    maxfiles    256         256 

この結果の「maxfiles」が今回のエラーの原因にあたります。

これを変更するために、設定ファイルを用意します。 最初は用意されていないはずなので、新規作成します。

$ touch /Library/LaunchDaemons/limit.maxfiles.plist

次に、作成したファイルに設定を書き込みます。

<?xml version="1.0" encoding="UTF-8"?>  
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">  
  <dict>
    <key>Label</key>
    <string>limit.maxfiles</string>
    <key>ProgramArguments</key>
    <array>
      <string>launchctl</string>
      <string>limit</string>
      <string>maxfiles</string>
      <string>1024</string>
      <string>1024</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>ServiceIPC</key>
    <false/>
  </dict>
</plist>  

最後に、設定を反映させるためのコマンドを実行します。

$ sudo launchctl load -w /Library/LaunchDaemons/limit.maxfiles.plist
$ sudo launchctl limit
    cpu         unlimited      unlimited      
    filesize    unlimited      unlimited      
    data        unlimited      unlimited      
    stack       8388608        67104768       
    core        0              unlimited      
    rss         unlimited      unlimited      
    memlock     unlimited      unlimited      
    maxproc     2837           4256           
    maxfiles    1024         1024

バッチリですね。

まとめ

やんごとなき理由で、AWS ELBの暖気申請ができない方は自前でPre-Warmingを行うこともできます。 その場合、負荷テスト用のコマンド(今回はApache Benchを紹介)を使用することになりますが、macOSでは同時に開くファイル数の上限に達してしまうエラーで阻まれてしまいます。 これを回避する方法は2通りあります。

  1. ulimitコマンドで一時的に上限設定する
  2. launchctlコマンドで恒久的に上限設定する

AWS障害が発生した場合に確認するページやサイトまとめ

概要

2019年8月23日 13時頃からAWS EC2の接続ができなくなる障害が発生しました。
このような大規模障害は滅多にないので、障害の情報収拾する際に「どこみりゃいいんだ?」となるので、この機会にまとめることにしました。

この記事ではAWSで障害が発生した場合に確認するページやサイトをまとめます。

公式情報

公式の情報は正確性はあるものの、速報性には欠けます。
そのため、後述する非公式情報と並行して確認する必要があります。 公式情報からは、下記2つの情報が得られます。

  • 何がなぜ障害に繋がっているのか
  • いつ復旧する見込みなのか

この情報から、障害を回避するための方法や、自サービスの復旧見込みのアナウンス*1に役立てることができます

AWSサービス全体の障害情報

AWSサービス全体の障害情報はAWS Service Health Dashboardで確認することができます。

status.aws.amazon.com

このページは、大きく最新の情報(Recent Events)と過去7日間の障害履歴(Status History)が表示されます。 また、Recent Eventsには障害の状況も確認することができます。

f:id:watasihasitujidesu:20190823144855p:plain

自分のアカウントで影響を受けているAWSサービス

AWSサービス全体のうち、自分のアカウントで影響を受けている障害情報は、AWS Personal Health Dashboardで確認することができます。

aws.amazon.com

f:id:watasihasitujidesu:20190823150903p:plain

ここで表示される情報は、基本的にはAWS Service Health Dashboardと同一です。 また、AWS Personal Health Dashboardは、CloudWatch Eventsと統合することができるので、障害が発生した場合にいち早く通知を受け取ることも可能になります。

AWS CLI

先述した2つのダッシュボードで障害情報を確認することができますが、CLIでも確認することができます。

docs.aws.amazon.com

CLIでは下記オプションを指定することで、細かくフィルタリングすることができます。

  • describe-affected-entities
  • describe-entity-aggregates
  • describe-event-aggregates
  • describe-event-details
  • describe-event-types
  • describe-events

用途としては、下記の場合に使えるかと思います。

  • 7日以上前の障害状況と復旧までの時間をログとして残しておきたい
  • 特定の情報のみ操作したい

非公式情報

非公式の情報は正確性には欠けるものの、速報性があります。
AWS障害と思われる事象が観測された場合に、他のAWSユーザーも同様の状況に陥っているかを素早く確認することができます。 そのため、AWSの障害なのかを切り分けるための初手としては良い手だと思います

Twitter awsハッシュタグ

基本的には、Twitterを見れば誰かしらつぶやいていることが多いです。
この場合、最新タブに切り替えて、みんなの速報を確認します。

https://twitter.com/search?q=%23aws&src=typed_query&f=live

Twitter非公式アカウント

AWSの非公式アカウントで障害情報の速報を流しています。 今回の障害では見ることはありませんでしたが、「こういうのもあるよ」程度で知っておくと良いと思います。

まとめ

情報には、公式情報と非公式情報があります。 それぞれ、メリットとデメリットは下記の通りです。

  • 公式: 正確性はあるものの、速報性には欠ける
  • 非公式: 正確性には欠けるものの、速報性がある

そのため、障害が発生した場合、公式情報と非公式情報をうまく使い分けて、情報収拾をする必要があります。
また、公式の情報には、何がなぜ障害に繋がっているのか、いつ復旧する見込みなのかという情報が公開されます。
この情報から、障害を回避するための暫定対応策と、自サービスや社内メンバーへのアナウンスを考える事ができます。

*1:ユーザーさんや社内メンバーに向けて

SnapmartのブログサーバーのSSL証明書をLet's EncryptからAWS Certificate Manager (ACM)に移管しました

概要

先日、SnapmartのブログサーバーのSSL証明書が有効期限切れになってしまい、アクセスすることができなくなってしまいました。

これまでSnapmartのブログサーバーではLet's Encrypt という証明書を無料で発行しているサービスを使っていました。
しかし、このLet's Encryptで発行される証明書は有効期限が90日間で設定されているため、それに気づかず失効してしまいました。

Let's Encryptの使用による90日ごとに証明書の更新を行うのは今回のようなトラブルの元になることや、運用負荷が高まってしまうため、このタイミングでSnapmartのブログサーバーのSSL証明書をLet's EncryptからAWS Certificate Manager (ACM)に移管しました

当初の構成と運用

当初のシステム構成と、運用はこのような感じでした。

f:id:watasihasitujidesu:20190812155930p:plain

  • オリジンをEC2(WordPress)としたCloudFrontを使用
  • 証明書の発行はLet's Encryptが行う
  • CloudFrontに設定する証明書はACMで管理しているが、Let's Encryptで発行した証明書インポートしている

「CloudFrontを設置し、負荷対策と高速化はできているが、証明書の発行と運用まで手が回っていなかった」という状態です。

あるべき姿

「Let's Encryptからの脱却し、運用負荷(考えるべきこと)を減らす」を目的に、代替手段を考えます。

メリット デメリット
証明書を代理店から購入し、失効までの期間を伸ばす 失効までの期間が延長される 数年に一度は更新作業をしなければならない
Let's Encryptの証明書更新、ACMインポート、CloudFrontの設定を自動化する 目の前の課題は自動化により解決される プラットフォームの変化に弱い
証明書の管理にACMを使う 証明書の更新や設定はAWSが管理してくれる EC2に証明書をもたせることができない

これらを比較検討した結果、「証明書の管理にACMを使う」が有力だろうと考えました。
しかし、デメリットを挙げた通り、EC2はACMが発行した証明書を持つことができないため、CloudFrontとEC2インスタンスの間にELBをかます必要があります。

移行手順

大まかな手順は下記の通りです。

  • ACMでSSL証明書を発行
  • ALBを設置し、ターゲットをEC2インスタンス(既存のブログサーバー)に設定
  • CloudFrontのオリジンにALBを設定
  • CloudFrontのSSLをACMで発行したSSL証明書に設定

ACMでSSL証明書を発行

何はともあれ、SSL証明書が必要です。
今回は、faq.snapmart.jpとinfo.snapmart.jpの2つのドメインが必要であったため、ワイルドカードを使った証明書を作る必要があります。

発行自体は非常に簡単で、 AWS ACMのマネコンにアクセス => Request a certificate => Request a public certificateと画面を進み、Domain nameにワイルドカードを使ったドメイン名(今回の例だと*.snapmart.jp)を設定するだけです。

f:id:watasihasitujidesu:20190812201302p:plain

ALBを設置し、ターゲットをEC2インスタンス(既存のブログサーバー)に設定

ALBの設定は特筆する点がありませんので、公式のドキュメントを参考に作成します。

docs.aws.amazon.com

CloudFrontのオリジンにALBを設定

対象のオリジンを選択 => Origins and Origin Groups => Editから「Origin Domain Name」に上記で作成したALB名を指定します。

CloudFrontのSSLをACMで発行したSSL証明書に設定

最後に、CloudFrontのSSL証明書を変更します。
対象のオリジンを選択 => General => Editから、
Custom SSL Certificateを編集します。
テキストボックスに、Focus Inすると、ACMで作成した証明書名が表示されるので、それを選択します。

f:id:watasihasitujidesu:20190814105211p:plain

以上で設定は終わりです。

まとめ

最終的には下記の構成図となりました。

f:id:watasihasitujidesu:20190814110944p:plain

Let's Encryptは手軽にSSL証明書を取得できる便利なサービスです。
しかし、証明書の期限が90日であるため、こまめに更新を行わなければならず、運用が煩わしくなったり、更新漏れが発生しWebサイトにアクセスができなくなってしまいます。

今回は、SSL証明書の運用から解放されるために、証明書の管理をAWS Certificate Manager (ACM)に移管しました。
また、EC2をオリジンにしたCloudFrontを使用している場合は、EC2は直接ACMの証明書を使えないため、CloudFrontとEC2の間にALBを置く必要があります。

【React Native】Expo SDK v33.0.0 へのアップグレード手順

概要

今年の1月にGoogleが「今後Androidアプリは64bit対応してないとダメだよ!8月1日以降32bitのアプリはリリースできないからね!」というアナウンスをしました。 android-developers.googleblog.com

SnapmartアプリはReactNativeで実装しており、開発ツールとしてExpoを使っています。ExpoはSDKのバージョン33から64bitのApp Bundleをビルドできるようになりました。 blog.expo.io

この記事ではExpo SDK v32.0.0からv33.0.0へアップグレードする手順を紹介します。

手順

app.jsonの修正

app.jsonのsdkVersionを"33.0.0"に変更します。

package.jsonの修正

package.json内に記述している、react-native, expo, reactをそれぞれ下記の通り変更します。

{
  "react-native": "https://github.com/expo/react-native/archive/sdk-33.0.0.tar.gz",
  "expo": "^33.0.0",
  "react": "16.8.3"
}

import文の修正

これまでexpoからのimportしていた各種APIが、SDK33からはexpoから分離し、別パッケージとして開発が行われるようになりました。 それに伴い、expoからのimportしていたコードは、分離されたパッケージからimportするように変更しなくてはなりません。

具体的にはこんな感じです。

  • Before
    • import { FileSystem } from 'expo';
  • After
    • expo install expo-file-systemを実行
    • import * as FileSystem from 'expo-file-system';

上記のような修正を全て手作業を行うのはとても骨が折れます。
Expoはこの処理を自動で修正するツールも開発しています。今回はそのツールを使用します。 www.npmjs.com

npx expo-codemod

使い方は非常に簡単です。

$ npx expo-codemod
$ npx expo-codemod sdk33-imports 'src/**/*.js' 'src/**/*.{ts,tsx}'
Transforming 13 TS files...
Processing 13 files... 
Spawning 11 workers...
Sending 2 files to free worker...
Sending 2 files to free worker...
Sending 2 files to free worker...
Sending 2 files to free worker...
Sending 2 files to free worker...
Sending 2 files to free worker...
Sending 1 files to free worker...
All done. 
Results: 
0 errors
12 unmodified
0 skipped
1 ok
Time elapsed: 0.471seconds 
Transforming 99 TSX files...
Processing 99 files... 
Spawning 11 workers...
Sending 9 files to free worker...
Sending 9 files to free worker...
Sending 9 files to free worker...
Sending 9 files to free worker...
Sending 9 files to free worker...
Sending 9 files to free worker...
Sending 9 files to free worker...
Sending 9 files to free worker...
Sending 9 files to free worker...
Sending 9 files to free worker...
Sending 9 files to free worker...
All done. 
Results: 
0 errors
37 unmodified
0 skipped
62 ok
Time elapsed: 1.661seconds 
Added imports from 9 packages.
To install the correct versions of these packages you can run this expo-cli command:

expo install @expo/vector-icons expo-analytics-amplitude expo-camera expo-constants expo-contacts expo-font expo-image-picker expo-permissions expo-secure-store

このような感じで自動で変換してくれます。 また、処理が終わったあとのメッセージに、expo installすべきパッケージが羅列されます。 最後に下記コマンドを実行すれば変更は完了です。

expo install @expo/vector-icons expo-analytics-amplitude expo-camera expo-constants expo-contacts expo-font expo-image-picker expo-permissions expo-secure-store

node_moduleの削除と再インストール

package.jsonを書き換えたので、node_moduleディレクトリを削除します。 その後、yarn installで新しくパッケージのインストールを行います。

Breaking changesの確認と修正

最後にBreaking changesを確認し、変更すべきものがあったらアプリのコードを修正していきます。 github.com

まとめ

Expo SDKのバージョンアップは基本的にapp.jsonとpackage.jsonを修正してyarn installするだけで済みます。 SDK33においては、ExpoのAPIを独立させる動きがあっため、自動変換ツールを使用してパッケージのimport方法を変更しました。 自動変換ツールをExpoが提供してくれているので、特にハマることもなく変換できました。 最後のBreaking changesの確認と修正は、アプリの規模によって修正箇所が増減しそうです。 Snapmartアプリは影響があった修正は数カ所であったため、比較的スムーズにアップグレードが完了しました。

また、Expo SDKは下位互換性維持期間はリリースされてから半年であることや、半年をすぎるとbuildできなくなります。 そのため、Expoを使っている場合は、定期的にSDKのアップグレードを行なう必要があります。

ReactNative 0.59.8のCameraRoll.getPhotosでInvalid filter option: '(null)'. Expected one of 'photos','videos' or 'all'.が発生した場合の回避方法

概要

タイトルの通り、ReactNative 0.59.8において、CameraRoll.getPhotos({first:1234})がエラーになってしまいます。
また、これはiOSのみで発生します。
エラー文を見た感じ、optionとしてassetTypeに何かしら指定すれば良さそうです。

ただ、ドキュメントを見る限り、"Photos"がdefaultなので普通に不具合っぽいですね。

facebook.github.io

回避方法

先述の通りassetType: "Photos"を指定すれば良いかと思いきや、groupTypesも指定しなければうまく取得できませんでした。 最終的には下記のようにコードを書き換えるとうまく動作します。

CameraRoll.getPhotos({first: 1234, assetType: "Photos", groupTypes: "All"}) 

追記2019/07/21 19:23 JST

上記の修正を行なった場合、Android版ではgroupTypesが存在しない旨のエラーが出てしまうため、Platformによりコードを修正せざるを得ない。