Expo + React Native Debugger でデバッグする

React NativeでDOM要素をデバッグするとき、Web開発のようにChromeデベロッパーツール風の画面でデバッグする方法があったので書きます。 Expo XDEとAndroidiOSのシミュレータが必要になるので、インストールしておいて下さい。

react-native-debugger のインストール

github.com

$ brew update && brew cask install react-native-debugger

Expo XDE でプロジェクトを開く

起動したら、歯車アイコンをクリックして、Host -> LAN を選択。Development Modeにチェックが入っていることを確認。

f:id:kinakobo:20180121022038p:plain

DeviceアイコンからiOSAndroidのシミュレータを起動。

React Native Debugger を起動

Expoはport19001でデバッガを実行するので、React Native Debuggerにportを指定して起動する必要があります。

$ open "rndebugger://set-debugger-loc?host=localhost&port=19001"

デバッグする

シミュレータでExpoアプリの設定画面を開き「Debug Remote JS」を押すと、デバッグができるようになります。

f:id:kinakobo:20180121023010p:plain

f:id:kinakobo:20180121023613p:plain

参考:

www.gravitywell.co.uk

Cloud Function から Cloud SQL を使う方法

Cloud Functions for Firebase から Cloud SQLMySQL)を使いたかったのですが、現状公式でトリガーが用意されていませんでした :cry:

Cloud Functions for Firebase  |  Firebase

調べたところ、nodejsのmysqlライブラリを使えばできました。

const mysql = require("mysql");

exports.handler = function handler(req, res) {
  const c = mysql.createConnection({
    socketPath: "/cloudsql/" + "$PROJECT_ID:$REGION:$DBNAME",
    user: "$USER",
    password: "$PASS",
    database: "$DATABASE"
  });
  
  c.connect();
  c.query(`SELECT * FROM table`, (e, results) => {
    // callback
  })
}

参考:

stackoverflow.com

ReactNavigationで遷移先に渡した値を this.props.hoge で参照したいとき

ReactNavigationで遷移先の画面に値を渡したいとき、

navigate('SecondScreen', { user: 'Hoge' })

のように指定すれば渡せますが、遷移先で値はthis.props.navigation.state.pramas.userに入ります。

そうではなく this.props.user に入っていてほしかったので調べたところ、以下のようにすれば解決しました。

const mapNavigationStateParamsToProps = (ScreenComponent) => {
  return class extends React.Component {
    static navigationOptions = ScreenComponent.navigationOptions;
    render() {
      const { params } = this.props.navigation.state;
      return <ScreenComponent {...this.props} {...params} />;
    }
  };
};

const MainNavigator = StackNavigator({
  firstScreen: { screen: mapNavigationStateParamsToProps(FirstScreenComponent) },
  secondScreen: { screen: mapNavigationStatePramasToProps(SecondScreenComponent) }
});

参考: github.com

CRNAで作成したReactNative開発環境にTypeScriptを導入する

前回CRNAでReactNativeの開発環境を構築したので、その続きでTypeScriptを導入していきます。

TypeScriptをインストール

$ yarn add --dev typescript

yarn add v1.3.2
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
warning "jest-expo > babel-jest@21.2.0" has unmet peer dependency "babel-core@^6.0.0 || ^7.0.0-alpha || ^7.0.0-beta || ^7.0.0".
[4/4] 📃  Building fresh packages...
success Saved lockfile.
success Saved 1 new dependency.
└─ typescript@2.6.2
✨  Done in 10.84s.

expoのTypeScript型定義ファイルが足りないため、@typesパッケージをインストール

$ yarn add --dev @types/expo

yarn add v1.3.2
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
warning "jest-expo > babel-jest@21.2.0" has unmet peer dependency "babel-core@^6.0.0 || ^7.0.0-alpha || ^7.0.0-beta || ^7.0.0".
warning "react-native-scripts > xdl > glob-promise@3.3.0" has unmet peer dependency "glob@*".
[4/4] 📃  Building fresh packages...
success Saved lockfile.
success Saved 4 new dependencies.
├─ @types/expo@24.0.3
├─ @types/fbemitter@2.0.32
├─ @types/react-native@0.52.1
└─ @types/react@16.0.34
✨  Done in 19.22s.

ちなみに@typesパッケージは以下で探せます

TypeScript Types Search

設定ファイルを書く

tsconfig.json

{
  "compilerOptions": {
    "target": "ES2017", // tsではなくbabelのasync/awaitを使うため、targetを高く設定する
    "module": "ES2015",
    "jsx": "react-native", // 成果物を読みやすくするため、jsxで生成する
    "outDir": "./dist",
    "noImplicitAny": true, // 暗黙のany型の宣言や式を警告する
  },
  "exclude": ["node_modules", "dist"]
}

ビルド

$ tsc -w -p .

以上でTypescriptの導入完了です

ReactNativeの開発環境をサクッと構築

アプリを作ってみたくなり、Web開発者としてとっつきやすそうなReactNativeに入門してみました。 この記事の内容は、開発環境構築から実機で動作確認するところまでです。

環境

  • macOS Sierra
  • Node.js v9.2.1
  • Android / iOS の実機 expoアプリをインストールしておきます

今回は試してませんが実機の代わりにシミュレータでも動作します。

XDEをインストール

以下のリンクからExpo SDKをDLしてインストールします。 起動するとアカウント登録を促されるので登録します。

CRNAをインストール

面倒な環境構築をすっ飛ばしてくれるすごいやつ。 好みでyarnを使ってインストールしていますが、もちろんnpmでもできます。

$ yarn global add create-react-native-app

yarn global v1.3.2
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 📃  Building fresh packages...
success Installed "create-react-native-app@1.0.0" with binaries:
      - create-react-native-app
✨  Done in 28.44s.

インストールしたら早速アプリを作成しましょう。 名前は何でもいいのでtest-appとしました。

$ create-react-native-app test-app

Creating a new React Native app in /Users/yuto/practice/react-native/test-app.

Using package manager as yarnpkg with yarn interface.
Installing packages. This might take a couple minutes.
Installing react-native-scripts...

...

Success! Created test-app at /Users/yuto/practice/react-native/test-app
Inside that directory, you can run several commands:

  yarn start
    Starts the development server so you can open your app in the Expo
    app on your phone.

  yarn run ios
    (Mac only, requires Xcode)
    Starts the development server and loads your app in an iOS simulator.

  yarn run android
    (Requires Android build tools)
    Starts the development server and loads your app on a connected Android
    device or emulator.

  yarn test
    Starts the test runner.

  yarn run eject
    Removes this tool and copies build dependencies, configuration files
    and scripts into the app directory. If you do this, you can’t go back!

We suggest that you begin by typing:

  cd test-app
  yarn start

Happy hacking!

作成されたディレクトリに移動し、開発サーバーを起動します。

$ cd test-app/
$ yarn start

See https://git.io/v5vcn for more information, either install watchman or run the following snippet:
  sudo sysctl -w kern.maxfiles=5242880
  sudo sysctl -w kern.maxfilesperproc=524288

watchman を入れてねと言われたのでインストールします。 watchmanファイルシステムの監視ツールです。

$ brew install watchman

気を取り直して再度起動します。

$ yarn start

うまくいくとビルドがはじまり、ターミナルにQRコードが表示されます。 iOSAndroidの実機を用意し、expoアプリから上記のコードを読み込むと、実機で動作確認することができます。

f:id:kinakobo:20171225004317p:plain:w200

コードの変更もすぐに反映されます。お手軽すごい。

ついでにコンポーネントライブラリを入れて遊んでみます。 NativeBaseをインストール

Getting Started · NativeBase

$ yarn add native-base
$ yarn add @expo/vector-icons

App.jsを以下のように変更しました。 NativeBaseにはカスタムフォントが含まれるため、コンポーネントレンダリングする前にフォントをロードするようにしました。 他にはrender内に公式のサンプルコンポーネントのコードを適当に追加しています。

import React from 'react';
import { Container, Header, Content, Footer, FooterTab, Button, Icon, Text } from 'native-base';
import Expo from 'expo';

export default class App extends React.Component {
  state = { fontLoaded: false };

  async componentWillMount() {
    await Expo.Font.loadAsync({
      'Roboto': require('native-base/Fonts/Roboto.ttf'),
      'Roboto_medium': require('native-base/Fonts/Roboto_medium.ttf'),
    });

    this.setState({fontLoaded: true});
  }

  render() {
    return (
      <Container>
        <Header />
        <Content />
        {
          this.state.fontLoaded ? (
            <Footer>
              <FooterTab>
                <Button vertical>
                  <Icon name="apps" />
                  <Text>Apps</Text>
                </Button>
                <Button vertical>
                  <Icon name="camera" />
                  <Text>Camera</Text>
                </Button>
                <Button vertical active>
                  <Icon active name="navigate" />
                  <Text>Navigate</Text>
                </Button>
                <Button vertical>
                  <Icon name="person" />
                  <Text>Contact</Text>
                </Button>
              </FooterTab>
            </Footer>
          ) : null
        }
      </Container>
    );
  }
}

実機で確認 👀

iOS Android
f:id:kinakobo:20171225003249p:plain:w200 f:id:kinakobo:20171225003258p:plain:w200

いい感じにフッターが iOS / Android のUIで表示されました。

【Scala】型パラメータを指定せずに呼び出せるメソッド

Option型に対して中身があるかどうかを判定するメソッドを書いている時に、Optionの中身の型は気にしないで判定する共通のメソッドがほしくなりました。

以下のように型パラメータを指定してあげれば、メソッドを呼ぶ時に型を指定しなくても型推論が効いてくれて動作しました。

scala> def require[T](param: Option[T]): T = {
     |   param match {
     |     case Some(x) => x
     |     case None    => throw new Exception
     |   }
     | }
require: [T](param: Option[T])T

メソッドを呼び出してみる

scala> val hoge: Option[Int] = Option(123)
hoge: Option[Int] = Some(123)

scala> require(hoge)
res0: Int = 123

scala> val fuga: Option[String] = Option("aaa")
fuga: Option[String] = Some(aaa)

scala> require(fuga)
res1: String = aaa

scala> val none: Option[String] = Option(null)
none: Option[String] = None

scala> require(none)
java.lang.Exception
  at .require(<console>:14)
  ... 29 elided

ScalikeJDBCでページネーションに必要なクエリを発行する

ScalikeJDBCを使ってページネーションするにはどうすればよいか調べました。 DBはMySQLを使用します。

MySQLで発行するクエリ

SELECT SQL_CALC_FOUND_ROWS * -- LIMIT を付けなかった場合に返されるはずの行数を知るためのオプション
FROM users
LIMIT 10 -- 取得する行数を制限する
OFFSET 5; -- 開始位置(行数)

SELECT FOUND_ROWS(); -- 直前に実行したクエリの行数を取得する

ポイント:FOUND_ROWS()

直前に実行したクエリの行数を取得する 直前に実行したクエリにSQL_CALC_FOUND_ROWSが指定されていた場合、「LIMIT を付けなかった場合に返されるはずの行数」を取得できる

ScalikeJDBCで書く

DSLを使って書くとこんな感じ

withSQL {
  select(sqls"sql_calc_found_rows *")
    .from(User as u)
    .limit(10)
    .offset(5)
}.map(User(u)).single.apply()

SQL("select found_rows() as total").map(_.long("total")).single.apply()

ハマったところ

メソッド内で上のコードを実行すると、SQL操作ごとに新しいセッションを開始するためか、select found_rows()のクエリで直前のクエリを認識できない(totalCountが1件になる)

def totalCount(implicit session: DBSession = AutoSession): Option[Long] = {
  withSQL...
}

res: Option[Long] = Some(1) // 1件になってしまう

解決策:DB.readOnly で囲えば正しい結果を取得できた

def totalCount: Option[Long] = {
  DB readOnly { implicit s =>
    withSQL...
  }
}

res: Option[Long] = Some(3) // 正しい結果がとれる

--- 12/25 追記 ---

Call back function after list.apply · Issue #63 · scalikejdbc/scalikejdbc · GitHub

AutoSession starts new session for each SQL operation

ScalikeJDBCの AutoSessionSQL操作ごとに新しいセッションを開始する仕様とのことなので、 ReadOnlySession を使ってクエリ実行後に session.close する方法なら、DB.readOnly で囲わなくても良さそうです。