こけこっこー

がんばる

React Native ElementsのButtonコンポーネントで何故か隙間ができるときの対処法

React Native ElementsのButtonコンポーネントを使っていたところ謎のpadding的な隙間ができて困りました。 こんな感じです。

f:id:monisoi:20180622071332p:plain

  nextButtonWrapper: {
    flex: 1,
    height: '70%',
    backgroundColor: '#CCC3BA',
  },
  nextButton: {
    height: '100%',
    width: '100%',
    backgroundColor: 'steelblue',
  },
const renderNextButton = dispatch => (
  <View style={styles.nextButtonWrapper}>
    <Button onPress={() => dispatch({ type: GO_TO_NEXT_TURN })} title="次へ" buttonStyle={styles.nextButton}/>
  </View>
);

青と灰色をすべて含む部分がViewコンポーネントnextButtonWrapper のスタイルを適用してます。 その中にButtonコンポーネントを入れていて nextButton のスタイルを適用してます。 nextButton はwidth, heightどちらも100%を指定しているので青い部分が灰色の部分まで広がることを期待しています。 つまりボタン押しできる範囲をviewコンポーネントの範囲全体に広げたいのです。

結局Buttonコンポーネントではうまく行かなかったのでReact Nativeの TouchableOpacity を使いました。 TouchableOpacity これで範囲全体をクリックできるようになります。

  nextButton: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    height: '70%',
    backgroundColor: '#CCC3BA',
  },
const renderNextButton = dispatch => (
  <TouchableOpacity style={styles.nextButton} onPress={() => dispatch({ type: GO_TO_NEXT_TURN })}>
    <Text>次へ</Text>
  </TouchableOpacity>
);

React Nativeでstyleのオブジェクトを統合する

ReactNativeではstyleをStyleSheet.create()して作ります。 以下のようなstyleを作りました。

const styles = StyleSheet.create({
    styleA: {
      width: '30%',
      height: '42%',
    },
    styleB: {
      backgroundColor: '#CCC3BA',
    }
});

styleAstyleB を統合させたくて以下のようにやったのですが、エラーになってしまいました。

const styleAB = {...styles.styleA, ...styles.styleB};

そもそも styles.styleAStyleSheet.create() によって生成されているので、出力がnumberになってしまうようです。 なので、上記のようなことがしたければ、 StyleSheet.flatten() を使う必要があります。

const styleAB = StyleSheet.flatten([styles.styleA, styles.styleB]);

ReactNativeでstyleをグローバルに設定する

アプリ全体のText Componentのcolorを一括指定してしまいたい、というときのやり方。 公式で推奨されているやり方によると、アプリ専用のコンポーネントを作成してそれを使いなさい、ということみたいです。

facebook.github.io

具体的には /custom/Text.js みたいなファイルを用意して、以下のように書けば良さそうです。

import React, { Component } from 'react';
import { Text, StyleSheet } from 'react-native';

const styles = StyleSheet.create({
  appText: {
    fontSize: 16,
    color: '#66605D',
  },
});

export class AppText extends Component {
  render() {
    return (
      <Text {...this.props} style={[styles.appText, this.props.style]}>
        {this.props.children}
      </Text>
    );
  }
}

実際に使用する側では、 import { Text } from 'react-native'; を書き換えて import { AppText } from '../custom/Text'; みたいに読み込んでやれば良いです。

ReactNativeElementsでButtonにiconを使う

ReactNativeElementsでボタンにアイコンを入れようと思ったらアイコンが表示されなくて焦りました。 自分が入れたバージョンとドキュメントのバージョンをよく見比べましょう。(戒め)

今回私が入れたバージョンは0.19系でした。 つまりドキュメントはこちらです。

react-native-training.github.io

どうやらアイコンはこんな感じで埋め込めるそうです。

<Button
  large
  icon={{name: 'envira', type: 'font-awesome'}}
  title='LARGE WITH ICON TYPE' />

一方、間違って見てたのはこっち。 新しいから検索でこっちが先に引っかかりました。

react-native-training.github.io

今後はこんな書き方になるっぽいです。 タグで指定する感じ。

<Button
  icon={
    <Icon
      name='arrow-right'
      size={15}
      color='white'
    />
  }
  title='BUTTON WITH ICON COMPONENT'
/>

ついでに厄介だったのが、バージョンの違いでボタンの中で指定できるアイコンが違うことです。 0.19で sword-cross とかのアイコン使おうと思ったらエラーが出ました。

ドキュメントのバージョン確認大事。

ImageDataGenerator flow_from_directoryを使い、名前付きでクラスごとのデータを読み込む

kerasの ImageDataGenerator を使って画像を読み込み、kaggleの画像分類問題をやっていたのですが、 validationデータで良い正答率が出るにもかかわらず、testデータにするとうまく分類できない状況に陥りました。

原因は flow_from_directoryclasses を指定していなかったことでした。

現象

前提として、データは以下のようなディレクトリ構造で配置しています。

data/
    train/
        dogs/
            dog001.jpg
            dog002.jpg
            ...
        cats/
            cat001.jpg
            cat002.jpg
            ...
    validation/
        dogs/
            dog001.jpg
            dog002.jpg
            ...
        cats/
            cat001.jpg
            cat002.jpg
            ...

続いてコードです。

    train_generator = train_datagen.flow_from_directory(
        train_images, # 上記 train を指定
        target_size=(image_height, image_width),
        batch_size=batch_size,
        class_mode='categorical')

    validation_generator = test_datagen.flow_from_directory(
        test_images, # 上記 validation/ を指定
        target_size=(image_height, image_width),
        batch_size=1,
        class_mode='categorical')

flow_from_directoryclass_modecategorical にしておけば上記のディレクトリ構造からいい感じに分類をやってくれるそうです。 ということで、分類を test/validation/ 以下のディレクトリ名でやってくれると思ったのですが、そうではありませんでした。 このまま実行するとディレクトリ名に関係なく0からディレクトリ数までの数字を割り振って分類されます。 つまりdogs, catsではなく0, 1のように分類されるということです。

解決策

この問題を解決するためには classes を指定してやる必要があります。

    classes = ['dogs', 'cats']

    train_generator = train_datagen.flow_from_directory(
        train_images, # 上記 train を指定
        target_size=(image_height, image_width),
        batch_size=batch_size,
        classes=classes,
        class_mode='categorical')

    validation_generator = test_datagen.flow_from_directory(
        test_images, # 上記 validation/ を指定
        target_size=(image_height, image_width),
        batch_size=1,
        classes=classes,
        class_mode='categorical')

jsのcaseには中括弧をつけるべき

React Reduxで開発してて、Reducerに以下のようなコードを書いてたら case Bhoge で怒られました。

switch (type) {
  case A:
    const { hoge } = state;
    return {
      ...state,
      hoge: hoge + 1,
    };
  case B:
    const { hoge } = state;
    return {
      ...state,
      hoge: hoge + 2,
    };
  default:
    return state;
}

エラーメッセージは以下のように言ってます。 Duplicate declaration "hoge" (null)

どうやら hoge が二重定義されてるっぽいです。 以下のように括弧をつければ解決。 caseを括弧で閉じてやらないとスコープが制限されないんですね。。。

switch (type) {
  case A: {
    const { hoge } = state;
    return {
      ...state,
      hoge: hoge + 1,
    };
  }
  case B: {
    const { hoge } = state;
    return {
      ...state,
      hoge: hoge + 2,
    };
  }
  default: {
    return state;
  }
}

urllibでurlから画像ダウンロードするときにタイムアウトを設定する

pythonで画像ダウンロードする方法を調べたら出てきたのが urllib.request.urlretrieve を使う方法だったので、これを使っていました。

課題

しかしながら、たくさんの画像を一度にダウンロードしようとしたときにurllib.request.urlretrieve を使う方法だとタイムアウトをセットできず、 無駄に時間がかかってしまう問題がありました。

解決方法

urllib.request.urlopenタイムアウトをセットできます。 これを使えば問題を解決できそうです。

21.6. urllib.request — URL を開くための拡張可能なライブラリ — Python 3.6.5 ドキュメント

実装

実装は以下のように一度 read() でメモリ上に読み込んでからファイルに 書き込む必要があるみたいです。 timeoutは秒単位で設定できるということなので、10秒でセットしてみました。

try:
    data = urllib.request.urlopen(url, timeout=10).read()
    with open(file_name, mode="wb") as f:
        f.write(data)
except:
    print("failed to download")

うまく動いてそうです。