そーす

福岡在住のプログラマ

Kotlinでa == 1 && a == 2 && a == 3

前回の記事はJavaScripta == 1 && a == 2 && a == 3が成り立つにはどうすれば良いか、という内容でしたが元記事のブコメ

プロパティある言語(C#, Swiftなど)ならトリッキーなことしなくても普通にできちゃう

とりあえずKotlinのDelegation。

一番楽ですぐ思いつく方法はコレじゃないでしょうか

import kotlin.reflect.KProperty

class Delegate {
    var count = 0

    operator fun getValue(thisRef: Any?, property: KProperty<*>): Int {
        count++
        return count
    }
}

fun main(args: Array<String>) {
    val a by Delegate()
    if ( a == 1 && a == 2 && a == 3) {
        println("true")
    }
}

JavaScriptの比較演算子の話

stackoverflow.com

これ、よく思いつくなーと感心しました。

普段等価演算==使うことないのですが、==の場合だと上記の回答のようなことができるようです。

valueOfでも大丈夫だって書いてあったのですが、toStringvalueOfどっちも定義されている場合、toStringが呼ばれることはありませんでした。

% node
> const a = { toString: () => console.log("toString"), valueOf: () => console.log("valueOf") };
undefined
> a
{ toString: [Function: toString], valueOf: [Function: valueOf] }
> a == 1
valueOf
false
> a == "1"
valueOf
false
> a === 1
false
> a === "1"
false
>

valueOfのリファレンスには

developer.mozilla.org

文字列型の文脈にあるオブジェクトは toString メソッドを通じて変換されますが、

とあるんですが、

> console.log(a + " string")
valueOf
undefined string
undefined

文字列の文脈って上記のようなやつは違うんですかね。

Next.jsに動的ルーティングを追加する「next-routes」

Nextjsにはデフォルトでルーティング機能があります。

[root_directory]/pages/ディレクトリ構成がそのままルーティングになります。

しかし、RESTfulなURIにする場合はサーバーの設定をカスタマイズして手動でしなければなりません。

そのような動的なルーティングを追加するためのモジュールがnext-routesです。

github.com

動的なルーティングを追加

たとえば、/users/show?id=ryohlan/users/ryohlanというルーティングを貼りたい場合は

# routes.js
const routes = module.exports = require('next-routes')()

routes
.add('/users/:id', '/users/show')

このように定義して、

# server.js
const next = require('next')
const routes = require('./routes')
const app = next({dev: process.env.NODE_ENV !== 'production'})
const handler = routes.getRequestHandler(app)

const {createServer} = require('http')
app.prepare().then(() => {
  createServer(handler).listen(3000)
})

という風にサーバーの設定を変更することで

/users/ryohlanへのアクセスを/users/show?id=ryohlanで受けることができます。

クリックで遷移の時

Nextjsではクリック遷移の時はNextimport Link from 'next/link'でLinkコンポーネントを使うのですが、

next-routesにはLinkコンポーネントもあるので代用することで動的なルーティングを使うことができる

# routes.js
const routes = module.exports = require('next-routes')()

routes
.add('users_show', '/users/:id', '/users/show') # 第1引き数が/で始まらない場合はルーティングのIDとして使うことができる

# pages/users/show.js
import {Link} from '../routes'

export default () => (
  <div>
    <Link route='users_show' params={{id: 'ryohlan'}}>
      <a>Hello world</a>
    </Link>
  </div>
)

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;
            }
        };
    }
}