【React Native】React Native + Firebase + Expo 環境を構築する
React Native
+ Firebase
+ Expo
前回の記事では、React NativeにFirebaseを導入する方法を紹介しました。
しかしながら、通常のreact-native init
による開発は、実機デバッグをしたい場合に億劫です。一方、Expoを使った実機デプロイであれば楽に検証が可能です。
今回は会社の先輩にExpo対応可能であることを教えていたいたので、ExpoによるFirebase導入をやってみたいと思います。
Usage
今回はExpo公式のドキュメントを参考にしました。
Using Firebase - Expo Documentation
まずはcreate-react-native-app
でプロジェクトを作成。
$ create-react-native-app my-app
$ cd my-app
npm
でfirebaseをインストールします。
npm install --save firebase .
次に、前回の記事に従い2.3まで実施し、プロジェクト名とAPIキーを取得します。 その後、プロジェクトダッシュボードから「ウェブアプリにfirebaseを追加」をクリック。
するとプロジェクト設定を反映したfirebaseConfig
Javascriptオブジェクトのテンプレートが取得できるので、コピーしてsrc/App.js
に貼り付けます。
インストールしたfirebase
モジュールもインポートしてください。
import * as firebase from 'firebase'; const firebaseConfig = { apiKey: "<YOUR-API-KEY>", authDomain: "<YOUR-AUTH-DOMAIN>", databaseURL: "<YOUR-DATABASE-URL>", storageBucket: "<YOUR-STORAGE-BUCKET>" }; firebase.initializeApp(firebaseConfig);
これで準備完了です!簡単ですね!
Demo
先にExpo Cliantをインストールします。
ローカルをサーバーでアプリ立ち上げ、コンソールに表示されたQRコードを実機で読み込むとExpo上でアプリが起動します。
$ npm start
エラーが無く起動が完了したら成功です!
続き
参考
Using Firebase - Expo Documentation
Installation & Setup in JavaScript | Firebase Realtime Database | Firebase
【React Native】React Native + Firebase環境を構築する(iOS)
React Native + Firebase環境を構築する際のメモ。
Firebase
FirebaseはGoogleが提供するモバイルプラットフォームであり、クラウドDBや認証、アナリティクス、クラッシュレポートなど多くのサービスをサポートしています。今回はReact NativeによるモバイルアプリにこのFirebaseを導入したいと思います。
Usage
1. プロジェクト作成
1.1 $ react-native init
でReact Nativeプロジェクトを作成します。
$ react-native init my-app
1.2 firebase
のインストール
作成したプロジェクト内でreact-native-firebase
をインストールします。
$ cd my-app $ npm install --save react-native-firebase
2. FirebaseのiOS/Android設定ファイルを取得する
2.1 Firebaseにプロジェクトを作成
GoogleのFirebase公式ページへ遷移し、「使ってみる」を押す(Googleにログインしていない場合はログインする)
2.2 プロジェクト追加をクリック
2.3 プロジェクト名とIDを入力し、「次へ」を押す
2.4 プロジェクトのダッシュボードに移動、「iOSアプリにFirebaseを追加」を押す
2.5 アプリを登録
「iOS バンドル ID」は作成したプロジェクトと同名にします。
アプリ設定ファイル(iOS)(GoogleService-Info.plist)をダウンロードします。
取得した設定ファイルをios/[project]/
配下に置きます。
その後、ios/[project]/
配下でpod init
を使用し、Podfileを作成します。
$ pod init
Podfileを以下のように書き換えます。
target '[Project name]' do pod 'Firebase/Core' end
書き換えたら
$ pod install
2.6 AppDelegate.mを書き換え
[プロジェクト名].xcworkspace
をXCodeで立ち上げ、以下の記述を追加
Swiftの場合
import Firebase // 追加 ... func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { FirebaseApp.configure() // 追加
Objective-Cの場合
@import Firebase; // 追加 ... - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [FIRApp configure]; // 追加 }
デプロイ
$ react-native run-ios
参考
【React Native】react-native-swiperで画面スワイプを実装する
react-native-swiper
React Nativeでスワイプ画面遷移が可能になるreact-native-swiper
を試してみました。
Usage
1. 前回の記事に従い、create-react-native-app
をインストール
2. create-react-native-app
プロジェクトを作成
$ create-react-native-app react-native-swiper-app
$ cd react-native-swiper-app
3. react-native-swiper
をインストール
$ npm install --save react-native-swiper
4. デプロイ
$ npm start
Source
サンプルコードをもとにApp.jsを修正しました。
import React from 'react'; import { StyleSheet, Text, View } from 'react-native'; import Swiper from 'react-native-swiper'; const styles = StyleSheet.create({ slide: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#9DD6EB', }, text: { color: '#fff', fontSize: 30, fontWeight: 'bold', } }) export default class App extends React.Component { render() { return ( <Swiper showsButtons={true}> <View style={styles.slide}> <Text style={styles.text}>First Page</Text> </View> <View style={styles.slide}> <Text style={styles.text}>Second Page</Text> </View> <View style={styles.slide}> <Text style={styles.text}>Third Page</Text> </View> </Swiper> ); } }
Swiper
の小要素としてView
複数設定することで、設定した小要素間をスワイプで遷移可能になります。
Demo
Options
スワイプ方向を垂直にする
horizontal
属性でスワイプ方向を変更できます。
デフォルトはtrue
。
<Swiper horizontal={false}> ... </Swiper>
スワイプをループさせない
loog
をfalse
にすると、末尾ページから先頭ページへのスワイプを抑止できます。デフォルトはtrue
。
<Swiper showButton={true} loop={true}> ... </Swiper>
自動スワイプ
autoplay
をtrue
にすると、スワイプが自動化されます。
デフォルトはfalse
。
<Swiper autoplay={true}> </Swiper>
参考
【React Native】Expo + react-native-gifted-chat でチャットアプリを作る
最近React Nativeの開発を始めたのですが、いろいろなReact NativeのUIコンポーネントが配布されているようなので 適当にいじって遊んでみることにしました。
今回は、チャットUIとして提供されているreact-native-gifted-chatを試してみました。
react-native-gifted-chat
React NativeのUIコンポーネントです。
簡単にMessengerライクなチャット画面を作成することができます。
商用でも全然いけそうな見た目してます。
Expo
今回は実機(iOS)で開発するため、実機開発用のデプロイツールも使いました。
ExpoはオープンソースのReact Nativeビルドツールチェーンです。
Expoを使うことで、iOS/Android端末上へのアプリのビルド/デプロイが簡単に行なえます。
またソースコードの変更を即座に端末内のアプリに反映することができます。
Usage
早速作っていきます!
- モバイル端末にExpo Clientをインストールします。
$ npm install -g react-native create-react-native-app $ create-react-native-app my-app $ cd my-app $ npm start
出現したQRコードをモバイル端末で読み取ります。
するとExpoアプリを起動できる通知が来るので、通知をタップ。
アプリが起動したらOKです!
あとは react-native-gifted-chat
をnpm
経由でインストールしていきます。
$ npm install react-native-gifted-chat --save
src
公式のリポジトリを参考に、App.js
を下記のように書き換えてみました。
import React from 'react'; import { GiftedChat } from 'react-native-gifted-chat'; export default class App extends React.Component { componentWillMount() { this.setState({messages : []}); } reply(){ return { _id: 1, text: 'Hello!', createdAt: new Date(), user: { _id: 2, name: 'React Native', avatar: 'https://placeimg.com/140/140/any', } }; } onSend(messages = []) { this.setState(previousState => ({ messages: GiftedChat.append(GiftedChat.append(previousState.messages, messages), this.reply()), })) } render() { return ( <GiftedChat messages={this.state.messages} onSend={messages => this.onSend(messages)} user={{ _id: 1, name: 'you', avater: 'https://placeimg.com/140/140/any' }} /> ); } }
GiftedChat
が今回利用するチャットのUIコンポーネントになります。
messages
にはメッセージの履歴が渡され、
onSend
にはSendボタンタップ時の挙動が渡されます。
user
には操作する人のユーザ情報(名前やサムネイルなど)が渡されます。
Stateの持つmessages
にメッセージオブジェクトを追加していくことでどんどん履歴を追加できます。
メッセージオブジェクトのフォーマットは公式Githubかreply()
メソッドが返すオブジェクトを参照してください。
Demo
うまくできました! GiftedChatをそのままつかうだけでも結構リッチなChatUIを使うことができました。
参考
【JavaScript】async/await
非同期処理
JavaScriptでは非同期処理を行うような関数を実行するとPromiseオブジェクトが返ってくることパターンが多いです。
返ってきたPromiseオブジェクトに対してthen
メソッドを読んで非同期処理終了後の処理も記述できるので、呼び出す側がコールバックまみれになりにくく、良い感じです。
function caller(){ let promise = http(); // Promiseオブジェクトを返す関数 promise.then(response => console.log(response)); } caller(); console.log("caller() was called.");
この非同期処理はasync``await
でより簡単に記述することができます。
async/await
async
async
は非同期処理を完結に記述するための修飾子です。
async
を関数定義に修飾することで、非同期関数として定義することができます。
async function http(){ // ... }
関数にasync
を付加することで、呼び出し時にPromiseオブジェクトを返すようになります。
let promise = http(); console.log(promise); // Promise
返したPromiseオブジェクトは、async
を付加した関数が終了するタイミングでresolve
されます。
またasync
を付加した関数が例外を送出した場合はreject
されます。
await
await
はPromiseを返す式に修飾することで、Promiseに紐付いた非同期処理が終了するまでasync
関数内の処理が一時停止します。
async function http(){ await setTimeout(()=>("timeout."),3000); return "http response." }
一時停止したasync
関数の処理は、await
式の処理が終了して値が返されてから再開されます。
Usage
async function http(){ await setTimeout(()=>("timeout."),3000); return "http response." } function caller(){ let promise = http(); promise.then(response => console.log(response)); // 3秒後に出力 } console.log(caller()); console.log("caller() was called."); // 先に出力
参考
【AngularJS】ng-repeatの要素に一意な識別子を付加する
メモ。
AngularJSのng-repeat
によってHTML内の繰り返し要素を生成できますが、
HTML
<div ng-app="myApp"> <div ng-controller="MyAppCtrl"> <div ng-repeat="msg in messages"> <p>{{msg}}</p> </div> </div> </div>
JavaScript
let mod = angular.module('myApp',[]); mod.controller('MyAppCtrl', ['$scope', function($scope){ $scope.messages = ["hello" , "ng-repeat"]; }]);
要素型を文字列要素にした場合、同一の内容をiterableオブジェクトに格納すると以下のエラーが発生する場合があります。
JavaScript
let mod = angular.module('myApp',[]); mod.controller('MyAppCtrl', ['$scope', function($scope){ $scope.messages = ["hello" , "hello"]; }]);
Error: [ngRepeat:dupes] Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: node in tree, Duplicate key: string:hello
ng-repeat
では各要素に識別子を割り当てているが、デフォルトの識別子は$id(msg)
の返戻値となるようです。
要素が文字列型の場合、この返戻値が同一値になるため、識別子が重複しエラーとなるようです。
この問題を解決するには、ng-repeat
ディレクティブに渡す値にtrack by $index
を追加し、
各要素の識別子をインデックスにします。
<div ng-app="myApp"> <div ng-controller="MyAppCtrl"> <div ng-repeat="msg in messages track by $index"> <p>{{msg}}</p> </div> </div> </div>
参考
【HTML】<input type="file" />における脆弱性対策
<input type="file" />
<input type="file" />
は手軽にファイルアップロード機能を提供できるタグですが、安易に設定してしまうことで脆弱なアプリケーション・システムを作成できてしまいます。安易にファイルアップロード機能を実装するのではなく、予め潜在するリスクについて知り、その対策を出来る限り取っておく必要があります。
以下に、<input type="file" />
を実装する上で気をつけるベきポイントを調査し列挙しました。
実装例はあくまで参考程度のものです。動作は保証しません。
また実装例はクライアントのみですが、以下に示すValidationチェックは原則クライアント・サーバ双方での実装を心がけてください。
アップロードファイルタイプを限定する
.exe
などの実行ファイルによって、悪意あるスクリプトが実行されてしまう可能性を防げます。
<!-- 拡張子を指定 --> <input type="file" accept="image/jpeg,image/png" onchange="onChange(this)"/>
また念のため、スクリプト内でもチェックしましょう。
function onChange(this) { let file = this.target.files[0]; if (file.type !== 'image/jpeg' && file.type !== 'image/png' ) { // assertion error } }
アップロードファイルサイズを限定する
システム・サービスによっては別の仕様として決まっている場合もあると思いますが、
DOS攻撃を防ぐためにも、一定サイズ以上のファイルは弾くようにしてしまいましょう。
また複数ファイルアップロードをサポートする場合、一度にアップロードするファイル数も同様の理由で限定したほうが良いと思われます。
クライアント・サーバサイドでチェックします。
const MAX_FILE_SIZE = 1024 * 1024 * 10; // 10MB function onChange(this) { let file = this.target.files[0]; if (file.size > MAX_FILE_SIZE) { // assertion error } }
ディレクトリ構造を受け付けない
foo/bar/piyo.jpeg
のようなディレクトリ構造を受け付けず、ファイルのbasenameのみ受け付けるようにしましょう。
指定したファイルパスに対するレスポンス結果からサーバ側のディレクトリ構造を推測する、ディレクトリトラバーサル攻撃の標的になります。
許されるのであれば、ファイル名をハッシュ値やタイムスタンプに置き換えるのも良いと思います。
function onChange(this) { let file = this.target.files[0]; let filePath = file.name.split('/'); let tmpFilePath = file.tmp_name.split('/'); let baseName = filePath[filepath.length - 1]; let tmpBaseName = tmpFilePath[filepath.length - 1]; }
エスケープ処理
例えばSQLのクエリ引数に設定するなど、アップロードしたファイルの情報を別の処理系のパラメータに渡すときはそれらをエスケープ処理しましょう。
SQLの場合、バインド機構などがあればそれも利用しましょう。
本当は、多分やらなきゃいけないことはもっとあるはず。。。