そーす

福岡在住のプログラマ

react-native-firebaseのiOSのワークアラウンド

react-nativeでFirebaseを簡単に使えるようになるreact-native-firebaseというライブラリがあります。

rnfirebase.io

ですが、react-native v0.51.0ではiOSの設定がドキュメント通りでは動きません。

そのワークアラウンドについて書いておきます。

RNFirebaseをpodからLibrariesに直接インポートする

ドキュメント通りにreact-native linkをするとPodfile

   pod 'RNFirebase', :path => '../node_modules/react-native-firebase'

が追加されていると思いますが、これを消します。

次に、Finderを開いてnode_modules/react-native-firebase/ios/RNFirebase.xcodeprojXcodeLibrariesにドロップします。

f:id:saburesan:20171213005611p:plain 

f:id:saburesan:20171213005643p:plain

Linked Frameworks and LibrariesにlibRNFirebase.aを追加

f:id:saburesan:20171213010021p:plain

とりあえずこれで動きます。

FirebaseMessagingを追加する時

rnfirebase.io

FirebaseMessagingを追加するステップでAppDelegate.m#import "RNFirebaseMessaging.h"を追加する作業があるのですが、

このヘッダが見つからないのでビルドが通りません。

これはXcodeHeader Search Pathに以下のパスを追加する必要があります。

$(SRCROOT)/../node_modules/react-native-firebase/ios/RNFirebase/messaging

f:id:saburesan:20171213010542p:plain

これでFirebaseMessagingのビルドが通るはずです。

KotlinJSでReactNative書くのは今のところ無理っぽい

会社のブログにて、Kotlin x ReactNativeに挑戦した結果を書きました。

anect.hatenablog.com

KotlinJS面白い。

まだまだ発展途上なので今後に期待。

KotlinJSを動かす

先日Kotlin1.2がリリースされました。

blog.jetbrains.com

バックエンド、Webフロントエンド、Androidでコードの共通化ができるようになったとこのことです。

今回はその機能は触らないんですが、前にリリースされたKotlinJSとKotlinNativeには興味がありました。

で最近はずっとReactNative, ReactSPAばっかりやってて、ふと思いました。

React NativeのAndroidのコードはKotlin化は余裕

KotlinJSでKotlinでJSが吐き出せる

KotlinNativeでiOSが動かせる

「これはReactNativeはKotlinだけで動かせるのでは?」

ということで、色々試してみます。

KotlinJSを動かす

まずここからです。先は長い。

とりあえずハロワ

プロジェクト作成

f:id:anect:20171205163310p:plain f:id:anect:20171205163331p:plain

エントリポイントを作成

fun main(args: Array<String>) {
    console.log("Hello, World")
}

⌘9でビルド

f:id:anect:20171206102359p:plain

outディレクトリにコンパイル結果が入ってます。エラーは無かったようです。

HelloWorld.js

HelloWorld.js見てみましょう

if (typeof kotlin === 'undefined') {
  throw new Error("Error loading module 'HelloWorld'. Its dependency 'kotlin' was not found. Please, check whether 'kotlin' is loaded prior to 'HelloWorld'.");
}
var HelloWorld = function (_, Kotlin) {
  'use strict';
  function main(args) {
    console.log('Hello, World');
  }
  _.main_kand9s$ = main;
  main([]);
  Kotlin.defineModule('HelloWorld', _);
  return _;
}(typeof HelloWorld === 'undefined' ? {} : HelloWorld, kotlin);

Kotlinというグローバルオブジェクトが無いと駄目のようです。

kotlin.js

f:id:anect:20171206102606p:plain

outディレクトリにlibというディレクトリもできており、そのなかにkotlin.jsというのがあります。

これをグローバルオブジェクトとして定義するとHelloWorld.jsが動きそうです。

実行

> const kotlin = require('./out/production/HelloWorld/lib/kotlin');
undefined
> require('./out/production/HelloWorld/HelloWorld');
Hello, World
{}

動きました。

他に何ができるかドキュメントを読んでみます。

JSのコードをKotlin上で書く

Calling JavaScript from Kotlin - Kotlin Programming Language

js()を使うとJSのコードが動くみたいです。ちょっと試してみましょう。

fun main(args: Array<String>) {
    val time: String = js(" new Date().toString()")
    console.log(time)
}

コンパイル結果

if (typeof kotlin === 'undefined') {
  throw new Error("Error loading module 'HelloWorld'. Its dependency 'kotlin' was not found. Please, check whether 'kotlin' is loaded prior to 'HelloWorld'.");
}
var HelloWorld = function (_, Kotlin) {
  'use strict';
  function main(args) {
    var time = (new Date()).toString(); // 新しいコード
    console.log(time);
  }
  _.main_kand9s$ = main;
  main([]);
  Kotlin.defineModule('HelloWorld', _);
  return _;
}(typeof HelloWorld === 'undefined' ? {} : HelloWorld, kotlin);
> const kotlin = require('./out/production/HelloWorld/lib/kotlin');
undefined
> require('./out/production/HelloWorld/HelloWorld');
Wed Dec 06 2017 10:27:43 GMT+0900 (JST)
{}

動きました。

nodeで動かす前提でkotlin.jsをrequireするコードも追加してみましょう。

fun main(args: Array<String>) {
    js("require('./out/production/HelloWorld/lib/kotlin')")
    val time: String = js(" new Date().toString()")
    console.log(time)
}

コンパイル結果

if (typeof kotlin === 'undefined') {
  throw new Error("Error loading module 'HelloWorld'. Its dependency 'kotlin' was not found. Please, check whether 'kotlin' is loaded prior to 'HelloWorld'.");
}
var HelloWorld = function (_, Kotlin) {
  'use strict';
  function main(args) {
    require('./out/production/HelloWorld/lib/kotlin');
    var time = (new Date()).toString();
    console.log(time);
  }
  _.main_kand9s$ = main;
  main([]);
  Kotlin.defineModule('HelloWorld', _);
  return _;a
}(typeof HelloWorld === 'undefined' ? {} : HelloWorld, kotlin);
> const kotlin = require('./out/production/HelloWorld/lib/kotlin');
undefined
> require('./out/production/HelloWorld/HelloWorld');
Wed Dec 06 2017 10:37:43 GMT+0900 (JST)
{}

動いたけど…違う。自動でrequireして欲しい…

調べてみるとKotlin Compilerの設定でCommonJSの形にもできるようです。

f:id:anect:20171206105011p:plain

早速CommonJSにしてコンパイルしてみます。

(function (_, Kotlin) {
  'use strict';
  function main(args) {
    console.log('Hello, World');
  }
  _.main_kand9s$ = main;
  main([]);
  Kotlin.defineModule('HelloWorld', _);
  return _;
}(module.exports, require('kotlin')));

おっ、requireしてくれてる。 しかしデフォルトではlibkotlin.jsが吐かれるのでこれだとエラーが出ます。

そこで先程のKotlin Compilerの設定でDestination directoryという設定がありましたが、これをnode_modulesに変更します。

f:id:anect:20171206105650p:plain

コンパイルすると

(function (root, factory) {
  if (typeof define === 'function' && define.amd)
    define(['exports', 'kotlin'], factory);
  else if (typeof exports === 'object')
    factory(module.exports, require('kotlin'));
  else {
    if (typeof kotlin === 'undefined') {
      throw new Error("Error loading module 'HelloWorld'. Its dependency 'kotlin' was not found. Please, check whether 'kotlin' is loaded prior to 'HelloWorld'.");
    }
    root.HelloWorld = factory(typeof HelloWorld === 'undefined' ? {} : HelloWorld, kotlin);
  }
}(this, function (_, Kotlin) {
  'use strict';
  function main(args) {
    console.log('Hello, World');
  }
  _.main_kand9s$ = main;
  main([]);
  Kotlin.defineModule('HelloWorld', _);
  return _;
}));

大分変わりましたね。

ちゃんとnode_modulesにコンパイル結果が入ってます。

f:id:anect:20171206110107p:plain

実行してみます。

% node out/production/HelloWorld/HelloWorld.js
Hello, World

やったぜ。

KotlinでNodeのサーバー立てる

JSのライブラリをKotlin側から使うのはどうするのでしょうか。

とりあえず雰囲気で書いてみる

interface Http {
    fun createServer(onRequest: (req: Any, res: Res) -> Unit): Proxy

    interface Proxy {
        fun listen(port: Int, callback: () -> Unit)
    }

    interface Res {
        fun writeHead(statusCode: Int)
        fun end(message: String)
    }
}

fun main(args: Array<String>) {
    val http: Http = js("require('http')")
    http.createServer({ _, res ->
        res.writeHead(200)
        res.end("Hello World")
    }).listen(8080, { console.log(it)})
}

コンパイルは通る。

実行してみる。

% node out/production/HelloWorld/HelloWorld.js
/Users/Ryohlan/dev/kotlin/HelloWorld/out/production/HelloWorld/HelloWorld.js:37
    http.createServer_jnnk04$(main$lambda).listen_n53o35$(8080, main$lambda_0);
         ^

TypeError: http.createServer_jnnk04$ is not a function
    at main (/Users/Ryohlan/dev/kotlin/HelloWorld/out/production/HelloWorld/HelloWorld.js:37:10)
    at /Users/Ryohlan/dev/kotlin/HelloWorld/out/production/HelloWorld/HelloWorld.js:43:3
    at Object.<anonymous> (/Users/Ryohlan/dev/kotlin/HelloWorld/out/production/HelloWorld/HelloWorld.js:46:2)
    at Module._compile (module.js:635:30)
    at Object.Module._extensions..js (module.js:646:10)
    at Module.load (module.js:554:32)
    at tryModuleLoad (module.js:497:12)
    at Function.Module._load (module.js:489:3)
    at Function.Module.runMain (module.js:676:10)
    at startup (bootstrap_node.js:187:16)

ですよねー。

そもそもcreateServer_jnnk04$ってなってしまってるのでなんとかしないと

external

これはKotlinコンパイラにそれが生のJSインターフェースだと伝える手段です。

これをHttpにつけてコンパイルしてみます。

external interface Http {
    fun createServer(onRequest: (req: Any, res: Res) -> Unit): Proxy

    interface Proxy {
        fun listen(port: Int, callback: () -> Unit)
    }

    interface Res {
        fun writeHead(statusCode: Int)
        fun end(message: String)
    }
}

fun main(args: Array<String>) {
    val http: Http = js("require('http')")
    http.createServer({ _, res ->
        res.writeHead(200)
        res.end("Hello World")
    }).listen(8080, { console.log("localhost:8080")})
}
% node out/production/HelloWorld/HelloWorld.js
localhost:8080

f:id:anect:20171206112907p:plain

やったぜ。

ついでにrequireもexternalにします。

しかし問題が。

requireは読み込むモジュールごとに返り値が違うので通常だとコンパイルが通りません。

Dynamic Type

これはKotlinJSのための型定義です。

dynamic型を使うとその型はKotlinのタイプチェックから無視されるのでコンパイルが通ります。

今回だと

external fun require(path:String):dynamic

fun main(args: Array<String>) {
    val http = require("http")
    http.createServer({ _, res ->
        res.writeHead(200)
        ...

このようにして使うとcreateServerは補完には出ませんがタイプチェックを無視されるのでコンパイルは通ります。

補完を効かせるためにhttpの型をつけて最終的には以下のようになります。

external interface Http {
    fun createServer(onRequest: (req: Any, res: Res) -> Unit): Proxy

    interface Proxy {
        fun listen(port: Int, callback: (message: String) -> Unit)
    }

    interface Res {
        fun writeHead(statusCode: Int)
        fun end(message: String)
    }
}

external fun require(path:String):dynamic

fun main(args: Array<String>) {
    val http: Http = require("http")
    http.createServer({ _, res ->
        res.writeHead(200)
        res.end("Hello World")
    }).listen(8080, { console.log("localhost:8080")})
}

@JsModuleでrequireいらず

ドキュメント見てたら@JsModuleを使うとrequire要らずということが分かりました。

なので更にスマートに

@JsModule("http")
external object Http { // interfaceだと実装がないのでコンパイル通らない。
    fun createServer(onRequest: (req: Any, res: Res) -> Unit): Proxy

    interface Proxy {
        fun listen(port: Int, callback: (message: String) -> Unit)
    }

    interface Res {
        fun writeHead(statusCode: Int)
        fun end(message: String)
    }
}

fun main(args: Array<String>) =
        Http.createServer{ _, res ->
            res.writeHead(200)
            res.end("Hello World")
        }.listen(8080, { console.log("localhost:8080")})

コンパイル

(function (_, Kotlin, $module$http) {
  'use strict';
  var Unit = Kotlin.kotlin.Unit;
  function main$lambda(f, res) {
    res.writeHead(200);
    res.end('Hello World');
    return Unit;
  }
  function main$lambda_0(it) {
    console.log('localhost:8080');
    return Unit;
  }
  function main(args) {
    $module$http.createServer(main$lambda).listen(8080, main$lambda_0);
  }
  _.main_kand9s$ = main;
  main([]);
  Kotlin.defineModule('HelloWorld', _);
  return _;
}(module.exports, require('kotlin'), require('http')));

Next.js+FirebaseHostingで構築するサーバレスWebアプリケーション

github.com

Next.jsというReactアプリケーションをデフォルトでServerSideRenderingしてくれるライブラリがあります。

これをFirebaseHosting上にFirebaseFunctionsを使って構築することで、無料でサーバレスSPAを作る事ができます。

Next.jsをclone

Next.jsはサンプルがとても豊富です。

github.com

今回使うサンプルはwith-firebase-hostingです。

github.com

cloneしたらwith-firebase-hostingディレクトリをコピーして使いましょう。

Hello World

まずは依存ライブラリをインストールします。

npm i

そして

npm run next

でローカルサーバが立ち上がります。

デフォルトではlocalhost:3000ですね

f:id:saburesan:20171114103843p:plain

これは単にnextjsを起動しているだけで、Firebaseは全く関係ありません。

デプロイ

事前にFirebaseでプロジェクトを作っておいてください。

プロジェクトを作ったら.firebasercの<project-name-here>をプロジェクト名に置き換えます

// .firebaserc
{
  "projects": {
    "default": "<project-name-here>"
  }
}

これでFirebaseの設定は完了です。

npm run deploy

これで設定したプロジェクトにデプロイされます。

(npm run serveは恐らく動かないと思います。)

デプロイが完了するとアップデート先のURLがログに出るのでアクセスしてみると、

先程ローカルで動かした内容が表示されてるかと思います。

もし404や500が出た場合はFirebase consoleのFunctionsでログを見ることができるので確認して見てください。

f:id:saburesan:20171114110616p:plain

プロジェクトの構造

app

実際にコードを書いていくディレクトリです。

nextjsのルートディレクトリです。

普段使うnextjsと違うのはnext.config.jsファイルでコンパイルの保存先がfunctions/nextになっているところくらいでしょうか。

functions

デフォルトではこのディレクトリがFirebaseにデプロイされます。

appのコンパイル先でもあります。

public

これはFirebaseHostingのルートとなるディレクトリです。

placdholder.htmlというのが入ってますが、基本的に触ることはないです。

FirebaseHostingの設定でpublicは必須です。

存在していなければデプロイに失敗します。

Deployment Configuration  |  Firebase

firebase.json

Firebaseの細かい設定を書きます。

{
  "hosting": {
    "public": "public",
    "rewrites": [
      {
        "source": "**/**",
        "function": "next"
      }
    ]
  },
  "functions": {
    "source": "functions"
  }
}

デフォルトでは上のようになっています。

hostingの設定ですが、publicは必須でrewritewですべてのアクセスに対してFirebaseFunctionsのnext関数を起動するようになっています。

functionsの設定でFirebaseFunctionsのソースを指定しています。functions/index.jsの内容は

const functions = require('firebase-functions')
const next = require('next')

var dev = process.env.NODE_ENV !== 'production'
var app = next({ dev, conf: { distDir: 'next' } })
var handle = app.getRequestHandler()

exports.next = functions.https.onRequest((req, res) => {
  console.log('File: ' + req.originalUrl) // log the page.js file that is being requested
  return app.prepare().then(() => handle(req, res))
})

となっていて、exports.nextでnextをエクスポートしています。

writesの"function":"next"はこのnextを呼んでいます。

面倒なところ

デフォルトではfunctionsがデプロイされますが、functionsに含まれるappのコンパイルの成果物distはpagesに紐づくファイルだけなので、

例えばapp/package.jsonのdependenciesはfunctions/package.jsonに書き足して上げないといけません。

画像を扱う場合はapp/staticというフォルダに入れて使うのがnextjsでは一般的なのですが、このstaticもコピーしなければなりません。

他にも、起動サーバーをカスタマイズする場合などもコピーして…

という感じになってしまいます。

appにまとめる

一応、今私はfunctionsの内容をappに移して運用しています。

そうなるとデプロイに不要なファイルが多く含まれるので、firebase.jsonのignoreで指定しています。

app/index.jsはfunctionsのindex.jsをそのまま使い、サーバーをカスタマイズする場合は

exports.start = () => {
  app.prepare().then(() => {
    createServer((req, res) => {
      ...
    }).listen(port, (err) => {
      if (err) throw err;
      console.log(`> Ready on http://localhost:${port}`);
    });
  });
};

という感じにしました。

package.json

"scritps" : {
  "dev": "node -e \"require('./').start()\"",
  ...
}

"dependencies": {
  "firebase-admin": "^5.5.0",    // 追加
  "firebase-functions": "^0.7.3", // 追加
  ...
}

firebase.json

{
  "hosting": {
    "public": "public",
    "rewrites": [
      {
        "source": "**/**",
        "function": "next"
      }
    ],
    "ignore": [
      "**/.*",
      "**/node_modules/**",
      "app/src",
      "app/flow-typed",
      "app/pages"
    ]
  },
  "functions": {
    "source": "app"
  }
}

package.json追記したりstaticコピーしてくるよりこっちのほうがいいなーと思いました。

よくわからない問題

firebase serveが上手く動かない

404だったり、変更が全然反映されなかったり…

謎です。

ローカルでは問題無く動くが、デプロイすると動かない

デプロイするとstyled-jsxとbabel-runtimeが無いというエラーが出ます。

ローカルではnode_modulesに入っているのですが…

追加でインストールすると動くようになりました。

まとめ

ハマリポイントありますが、無料でWebアプリ作れるのは良いですね〜

ちなみに、github pagesだった自分のポートフォリオをNext.js+FirebaseHostingに移行しました。

ryo-hlan.firebaseapp.com

サーバレスなんで、アクセスの感覚が空くとやっぱり遅いけどインスタンスが起動しているときなら速度は問題無さそう。

f:id:saburesan:20171114164328p:plain

まぁコンテンツ少ないんでね…

そのうちブログも移行します。

AndroidにHostsを書き換えずにPCのHostsを反映させる

PC側のHostsを変更

変更してください。

ローカルPCにプロキシサーバを立てる

なんでも良いです。

node作りたい、と思っていましたが先駆者がいらっしゃったのでありがたく使わせていただきました。   qiita.com

ペッと貼って起動

% node proxy-server.js 
http proxy server started on port 8080

デフォルトPortは8080です。

Androidにプロキシサーバの設定をする

AndroidWifi設定からProxyサーバーをlocalhost:8080に設定します。

f:id:saburesan:20171102093653p:plain

Portforwardingする

Android端末を開発者モードにする必要があります。

GoogleChromehttp://chrome://inspect#devicesにアクセスします。

Port forwardingからAndroidlocalhost:8080をPCの8080に接続するように設定します。

f:id:saburesan:20171102094026p:plain

完了

PCのHostsの設定がAndroid側にも反映されてると思います。

React Native Debuggerがすごすぎて笑ってしまった

github.com

何も言わずに入れて欲しい。

react-native-debuggerはReact Native公式のChrome Debuggerを元に作られたデバッグツールです。

Reduxを利用している場合に力を発揮します。

というかRedux使ってないと旨味無いかもです…

でもRedux入れてるなら絶対使って欲しい。

とりあえずスクリーンショット載せます。

f:id:saburesan:20171027193500p:plain

まだ試した機能は多くないでですが、使った感じで利点をならべるとすると

  • Action, Stateの可視化が素晴らしい
  • 時系列順にAction、Stateのログが取れる
  • 任意のタイミングのActionによるStateのDiffが取れる
  • 任意のタイミングのActionまで状態を戻すことができる
  • Debugger上でActionを発行できる

こんな感じです。

時系列順にAction、Stateのログが取れる。StateのDiffが取れる

f:id:saburesan:20171028104354p:plain

図の「Actionのログ」が発行されたActionを時系列に並べたものです。

「ActionやStateの状態」は「Actionのログ」から選択したActionに関する詳細が見れます。

例えば、図だとSETUP_PAGE_STATE#SET_SETUP_FETCH_STATEを選択して、

右側のパネルのタブでActionを選択すると発行されたActionの詳細を見ることが出来ます。

Stateタブではその左Actionが発行された後のStateの状態が、

DiffタブではActionによって起こったStateのDiffを表示してくれます。

また、それぞれの表示形式は「Tree」「Chart」「Raw」の3パターンから選べ、

デフォルトはTree(図の状態)です。

これをChartに変えると…

f:id:saburesan:20171028104817p:plain

このような表示になります。

Rawはただのテキスト表示です。

任意のタイミングのActionまで状態を戻すことができる。

Actionのログは詳細を見るだけでなく、「Jump」「Skip」という動作が可能です。

Jump

https://gyazo.com/d94ec482b603027187b5632ecc260a00

JumpはStateの状態をそのActionが発行されたタイミングまで戻すことが出来ます。 また、そのタイミングから時系列順にActionを再生することも出来ます。

Skip

Skipは任意のActionの発行を無効にします。

あるアクションがバグを引き起こしているかどうかの検証などに使えそうです。

Debugger上でActionを発行できる

https://gyazo.com/4584122879b6ebf6ad53a478ade7b06d

Debugger上でActionを発行する機能があります。

Debugger上で発行したActionもActionのログに記録されJump, Skipなどの操作が可能です。

ちなみに、ActionはSkipしてからSweepボタンを押すと完全にログから削除できます。

まだまだな所

全然使い物にならねーってほどではないのですが、偶に起きるエラーです。

不安定

ちょくちょく接続エラーになります。その時はアプリ側の再起動が必要です。

Navigation系でページを戻ったりはできない

react-navigationを使っているのですが、Jumpで前のページの状態まで持っていくとエラーになります。

List系の'same key'エラーたまになる

Viewの更新が偶にうまくいかないことがあります。

まとめ

まだまだ試せていない機能が多いのですが、少し使っただけでもデバッグが非常に楽になりました。

f:id:saburesan:20171028121918p:plain

デバッガ上でテストする機能があり、Jest, Mocha, Tape, Avaのテンプレートから選べるというすごく有用そうな機能もあるので試してみたいと思います。

VisualStudioCodeに入れておくと便利なプラグイン3選

ただ自分が使っててコレないと不便ってやつです。

advanced-new-file

marketplace.visualstudio.com

ファイルの新規作成にパスを先に指定して作成できます。

デフォルトだと先にファイル作成して保存時にGUIで指定なのですごく面倒なので。

⌘+nに割り当てると更に便利。

Relative Path

marketplace.visualstudio.com

開いているファイルからあるファイルへの相対パスを出してくれます。

JSでimportする時とかコレないと不便。

ただ、すごく重い時がある。

Active File in StatusBar

marketplace.visualstudio.com

何でデフォルトで開いているファイルのパス出してくれないんだろ…

React NativeでマテリアルデザインのRippleエフェクトを実装する

Inputフォームに続き、マテリアルデザインのRippleエフェクトをReact NativeのJSのみで実装してみました。

コード

transformの方で最初実装してたのですが、タブを連続でタップすると表示が上手く行かないことがあったのでこちらで実装しました。

iOS

https://gyazo.com/82f5641f4ea34f1d7a0821f814b2aa9f

Android

https://i.gyazo.com/9e209d9fa1c1e98f73ad18b46aabdf0f.gif

BitriseCIでAndroidSDKのライセンスエラー

install-missing-android-toolsを使っていると、SDKがアップデートされた時に

* What went wrong:
A problem occurred configuring project ':app'.
> You have not accepted the license agreements of the following SDK components:
  [Android SDK Platform 26].
  Before building your project, you need to accept the license agreements and complete the installation of the missing components using the Android Studio SDK Manager.
  Alternatively, to learn how to transfer the license agreements from one workstation to another, go to http://d.android.com/r/studio-ui/export-licenses.html
* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
BUILD FAILED

というエラーでCIが失敗する事があります。

原因はこれです。

qiita.com

これをBitrise上でやります。

android-sdk-licenseをアップロード

自身のPCのの$ANDROID_HOME/licenses/android-sdk-licenseをアップロードします。

BitriseのWorkflow EditorCode signingタブのGENERATE FILE STORAGEでアップロードします。

Workflowでダウンロードする

install-missing-android-toolのStepの前にFile DownloaderStepを追加します。

そしてDownload source urlに先程アップロードしたandroid-sdk-licenseのダウンロードURLを、

Download destination pathには

$ANDROID_HOME/licenses/android-sdk-license

を指定します。

f:id:saburesan:20171011130303p:plain

これで問題なくCIが通ると思います。

React Nativeでマテリアルデザインのテキスト入力フォームを作った

gist.github.com

https://gyazo.com/7bf7d4237f42dd5630fd1af83e062c6d

AndroidiOSでInputTextのスタイルを合わせるのが面倒だなと思ってました。

iOSもマテリアルデザインに寄せてしまうのは最近だとアリだと思っているのでちょっと試しに作りました。

ざっと30分くらいで作ったので本物の機能は足りてないです。

ReactNativeは簡単にUIが作れるのであまりUIライブラリを入れずに自作したほうが良いですね。

今年はDroidkaigiのアプリをReact Nativeで作るんだ…

React Nativeで起動時にJS側にデータを渡す

facebook.github.io

iOSの方はちゃんとドキュメントがあるのに何でAndroidは無いんですかね…

といってもやり方は簡単です。

MainActivityでcreateReactActivityDelegateをオーバーライドして

ReactActivityDelegateのgetLaunchOptionsをオーバーライドするだけです。

public class MainActivity extends ReactActivity {

    @Override
    protected ReactActivityDelegate createReactActivityDelegate() {
        return new ReactActivityDelegate(this, getMainComponentName()) {
            @Nullable
            @Override
            protected Bundle getLaunchOptions() {
                Bundle initialProps = new Bundle();
                initialProps.putString("buildType", BuildConfig.BUILD_TYPE);
                return initialProps;
            }
        };
    }
}

熱盛りSlackBot

f:id:saburesan:20170913175357p:plain

Bitriseのビルド待っている間に熱盛り絵文字を投稿すると返信してくれるようにした。

笑ってもらえたので良かった(会社のSlack)

React Nativeのアニメーションで簡単なチュートリアル画面を作った

React Nativeで個人開発アプリを開発中なのですが、ちょうどアニメーション周りを結構調べていたので試しに簡単なチュートリアルを作ってみました。

f:id:saburesan:20170906143339g:plain

Component

ほとんどAnimatedを使いました。コンポーネントもデフォルトのAnimated.View, Text, Imageのみです。

一部LayoutAnimationを使っていますが、最初にLayoutAnimationを使っていた名残で変更していないだけです。

LayoutAnimationは導入も使い方も非常に楽なので気に入っていたのですが、複雑なアニメーションをしようとするとかなり面倒くさく、あまり自由度が無いところから使わなくなりました。

ARTを使ってSVGでやることも考えたのですが、Animatedでも十分パフォーマンスが出る結果だったので今回は採用していません。

saburesan.hatenablog.com

Easing

Easingもデフォルトのtimingとspringしか使っていません。

Sequence, Stagger, Parallel

アニメーションを連続で再生するためのAPIがありますが、今回はsequence, stagger, parallelを使いました。

facebook.github.io

ドキュメントを読んだだけだとStaggerの使い所があまりわからなかったのですが、

今回検索文字の入力の部分や、3回タップのアニメーションの所などで使っています。

Interpolate

今回、すべてのアニメーションはAnimated.Valueを使っています。

また、基本的にValueは0->1の変化のみとして、Animated.Valueの値をinterpolateメソッドで適宜補間する形にしました。

理由はValueの値をスタイルで直接使う値にしてしまうと同じタイミングで始まるアニメーションがあったときにAnimated.Valueを使いまわせないからです。

同じにタイミングでもEasingやDurationが違う場合などはParallelを使用していますが、

EasingもDurationも同じでいい場合が結構多くinterpolateで補間するほうがわかりやすくなりました。

Valueを0->1にしてinterpolateで補間することでアニメーション自体の定義とスタイルの定義を完全に別にして考える事ができるのでお薦めです。

ハマったこと

Animated.XXXにして無い時にエラーで上手く教えてくれない

これは私が初期にLayoutAnimationで組んで居た所をAnimatedに変更したときにコンポーネントの定義でAnimated.XXXにし忘れていたのですが、エラーが非常にわかりにくく手間取りました。

Devモードだとアニメーションが重すぎる

めっちゃ重たいです。デバッグ無理です。

Androidであれば開発モードのDev SettingsJS Dev Modeをオフにしてデバッグしましょう。

iOSAndroidと同じ事が出来ないのでReleaseビルドでデバッグしましょう。

position: ‘absolute'で指定しているAnimated.Viewを消すとそれ以降のアニメーションが重くなる

これはホントの所の原因がわかっていないのですが、フルスクリーン表示したViewをアニメーションが終わった後に描画しない処理(renderXXXがnullを返す)をすると、それ以降のアニメーションがやたら重くなる現象が起きました。

leftの値をいじって画面外に出しても重くなってしまい、完全な原因が謎です。

今回は一番最後にボタンをタップできるようにする必要があっただけのでフルスクリーンのViewにpointerEvents=“none”を設定することで解決できましたが、ちょっと謎ですね…

まとめ

今回やった範囲だとハマりどころが多いという感じはしませんでした。

JS側UIスレッド側のFPSも57~60位で特にパフォーマンスが悪くなるような事も特に無く、アニメーションは実際ネイティブで書くと面倒ですがAndroid/iOSどっちも同じように書けるのはかなりのメリットだなぁと思いました。

AnimatedのAPIはまだ使ってないものもありますし、ARTとAnimatedを組み合わせると更に自由度が上がると思いますので色々遊んでみたいと思います。

React Native ARTで遊ぶ

React Nativeでグラフを描くにはどうしたらいいのかと調べましたら、

React Native内にReact Native ARTというライブラリが在るのを知りました。

ドキュメントにも書いてないですが、開発はされているようです。

react-native/ReactNativeART.js at 0.48-stable · facebook/react-native · GitHub

Install

初期でiOSはライブラリの設定がされていないのでそれを行う必要があります。(RN47.0 現在)

Xcodeでプロジェクトを開き、

  1. Build Phases -> Link Binary with Libraries -> + (Add items) -> Add Other -> /node_modules/react-native/Libraries/ART/ART.xcodeproj

  2. Build Phases -> Link Binary with Libraries -> libART.a

で追加します。

これで完了です。Androidは最初から動きます

直線を描く

import React, { Component } from 'react';
import {
  AppRegistry,
  ART,
  Dimensions
} from 'react-native';

const {
  ClippingRectangle,
  Group,
  Surface,
  LinearGradient,
  Path,
  RadialGradient,
  Shape,
  Transform,
  Text
} = ART;

export default class ArtSample extends Component {
  render() {
    const { width, height } = Dimensions.get('window');
    const path = Path();
    path.moveTo(width / 2, height / 2);
    path.line(0, -100);

    return (
      <Surface width={width} height={height} >
        <Shape d={path} stroke="#AAA" strokeWidth="4" />
        <Shape d={`m${width / 2},${height / 2} v100`} stroke="#333333" strokeWidth="8" />
        <Text x={width / 2 + 30} y={height / 2} stroke="#444" strokeWidth="1">ポッキーだよ</Text>
      </Surface>
    );
  }
}

f:id:saburesan:20170831144354p:plain

ポッキーを描きました。

svgに慣れてるのであればあまり難しくなさそうですね。(pathじゃなくてShapeだけど)

ベジエ

2次ベジエ、3次ベジエ。

return (
      <Surface width={width} height={height} >
        <Shape d={path} stroke="#AAA" strokeWidth="4" />
        <Shape d={`m${width / 2},${height / 2} v100`} stroke="#333333" strokeWidth="8" />
        <Text x={width / 2 + 30} y={height / 2} stroke="#444" strokeWidth="1" font="bold 20px Futura">ポッキーだよ</Text>
        <Shape d={`m0,0 q0,${height},${width},${height} `} stroke="#777" strokeWidth="2" />
        <Shape d={`m0,0 c0,${height},${width},${height},${width},0`} stroke="#777" strokeWidth="2" />
      </Surface>
    );

f:id:saburesan:20170831151543p:plain

LayoutAnimation

LayoutAnimationも効きますね(意味のないアニメーション)

f:id:saburesan:20170831153914g:plain