読者です 読者をやめる 読者になる 読者になる

そーす

福岡在住のプログラマ

「ReactNativeってどうなの?」というタイトルでLTしてきました

www.slideshare.net

LTなんて初めてでした。

とりあえず、なんか友人からの質問やTwitterとか見てて疑問に思ってそうな事適当に並べて見ました。

なんというか、福岡でもReact/React Nativeをワイワイさせたいなーとか使ってる人と友達になりたいなーと

思ってLTしました。

とりあえずReactNativeの周知目的でザーッとReactNativeいいよ〜的な話をして最後に辛み述べた感じでなんとも微妙でした(反省

1人だけ、ReactNativeやってます!って人が話掛けてくれて嬉しかったです!!

しかし福岡だとまだまだなのかな…

ていうかアプリ界隈のコミュニティ無くね…

インフラとかサーバーサイドのコミュニティとか勉強会とかは多いんですがね…

初心者向けReact NativeハンズオンとかやってRN人口増やしたいとか思ってたけど、とりあえず興味持ってもらうためにLTもっと参加しないとなって思いました。

(解決)ReactNative+FirebaseではネイティブSDKを使ったほうが良いかも

(追記)解決しました。 やっぱりJavascriptCoreが原因でした。

github.com

f:id:saburesan:20170405095735p:plain

developers-jp.googleblog.com

ここで紹介されてたので、ネイティブSDK入れなくてもいけるのか〜

と思って色々試して見ました。

匿名ログインしてRealtimeDatabaseに書き込みは出来たのですが、Storageへのbase64形式での画像アップデートがぜーんぜん上手く行かなくて

エラーメッセージも

“Firebase Storage: String does not match format ‘base64

って出てるけどデコードしたら問題なかったし超悩みました。

んで、ひっさしぶりにDebug JS Remotelyを起動して見てみよう(基本的にiOSシミュレーターのログをtailするか, adb logcat でログ見てるので)と

起動してアップロードしてみたら、なんとアップロードが成功するではありませんか!!

これから分かるのはReactNativeのJSを実行する環境であるJavascriptCoreでは

FirebaseStorageのputStringの内部で使用される何かが実装されてない or バグがあるということでしょうか。

リファレンスにも書いてありますが、Debug JS Remotelyを実行するとReactNativeのJSはChrome上で動作します。

facebook.github.io

Debug JS Remotelyのオン/オフで成功/失敗が変わるのであればおそらくコレが原因でしょう…

FirebaseはAPIがたくさんあるので全て調べるのは面倒なので、おとなしくネイティブのSDKを入れることにします。

ReactNativeでiOS, Androidに最適化したUIを構成するには

最近は仕事でも個人でもReact Nativeでの開発がメインになっており、かなり知識が溜まってきました。

で、だいぶiOSAndroidに最適化したUIの構成ができるようになってきたのでメモ程度に残します。

OSごとのUIの分け方

ReactNativeは標準で幾つかiOS, Androidの処理を分ける機能があります。

リファレンス貼っときます。

facebook.github.io

ファイルごと分割する

ReactNativeの標準機能でXXX.ios.jsとXXX.android.jsという規則でファイル名を付けて

import XXX from './XXX';
const XXX = require('./XXX');

とするとiOSの時はXXX.ios.jsを、Androidの時はXXX.android.jsを自動でインポートしてくれます。

例えば、エラーをユーザに通知する機能を作るとします。

iOSではUIAlertControllerを使い、AndroidではToastを使うのが一般的だと思います。

その場合、

Alert.ios.jsは

'use strict';

import { AlertIOS } from 'react-native';

module.exports = {
  show: message => AlertIOS.alert(message),
 };

Alert.android.jsは

'use strict';

import { ToastAndroid } from 'react-native';

module.exports = { 
  show: message => ToastAndroid.show(message, ToastAndroid.LONG) 
};

呼び出し

import Alert from './Alert';

Alert.show('hoge');

気をつけることはファイル名とプロパティ名を揃えることです。

スタイルなどの値を分ける

処理自体を分ける場合はファイル分割が良いですが、スタイルを分けたい場合なども頻繁にあります。

そういうときはPlatform APIを使います。

例えばTextInputを使う場合、iOSAndroidではかなりあてるスタイルが異なります。

TextInputはiOSではheightの設定が必要だったり背景色やボーダーが無いとフォームが解りづらいので設定する必要があります。

Androidの方は元から適切なPaddingや高さ、入力フォームがわかるように下線が付いていたりしますので背景色やボーダーを追加する必要はありません。

そのときはPlatform.selectを使って

  height: Platform.select({ ios: 54 }),
  backgroundColor: Platform.select({ ios: '#EEE' }),
  borderColor: Platform.select({ ios: '#DDD' }),
  borderWidth: Platform.select({ ios: 0.5 }),

このようにすることで細かい分け方も可能です。

ios

f:id:saburesan:20170401134649p:plain

android

f:id:saburesan:20170401134746p:plain

特定のOSのみ適用されるプロパティを利用する

標準のコンポーネントのプロパティで特定のOSにしか意味を為さないプロパティがいくつかあります。

facebook.github.io

例えば上のリンクのViewコンポーネントにもいつくかあり

f:id:saburesan:20170401133855p:plain

上記のスクリーンショットのように[iOS][Android]という表記で書かれています。

また、表記が無くともプロパティに設定する値がOSごとに意味を為す/為さないがあるのでリファレンスはしっかり読みましょう。

ネイティブAPI/ネイティブViewの呼び出しで分ける。

標準に無いネイティブのAPIやViewを呼びたい場合などは必然的にXcodeとAndroidStudioを使ってコードを分ける必要がありますが、

この時にReactNative側から呼ぶインターフェースを統一しておけば

OSの違いを意識すること無く呼び出すことが出来ます。

ネイティブ連携はリファレンスを見ながら簡単に導入することが出来ます。

facebook.github.io

Imageコンポーネントは画像をキャッシュする

ReactNativeの標準のImageコンポーネントはキャッシュ機構があります。

なので、以前に取得したURLの画像の場合はキャッシュが読まれます(期限は無さそう)

同じURLの画像が更新される仕様のAPIなどではこれは厄介です…

ちなみにいくつかIssueが上がってました。

github.com

2017/03/20現在ではキャッシュの削除機構は存在せず、ワークアラウンドで対応するか、他のOSSライブラリで対応するしか無いようです。

ちなみに私はとりあえずMath.random()をパラメータに付加してキャッシュを回避する方法を取りました。

BackAndroidはremoveEventListenerではなくaddEventListenerの返り値のオブジェクトを使おう

facebook.github.io

Andoroidの戻るボタンが押された時のイベントをハンドリングするAPIです。 なんか公式ドキュメント見るとaddEventListerでセットしたハンドラの破棄はremoveEventListerを使わないといけないっぽいですが、 ソースを見てみる

addEventListener: function (
    eventName: BackPressEventName,
    handler: Function
  ): {remove: () => void} {
    _backPressSubscriptions.add(handler);
    return {
      remove: () => BackAndroid.removeEventListener(eventName, handler),
    };
  },

removeメソッドを持つオブジェクトを返してますね。

ちなみに現在のmasterではBackAndroidはDeprecatedになってます。

github.com

BackHandlerというAPIに変わるそうです。

github.com

NetInfo.isConnectedはiOSでは常にfalseを返すという罠

issue上がってます

github.com

再現方法

NetInfo.isConnectedを使うだけ。 Androidはちゃんとしたネットワークの接続状況を返してくれますが、iOSは常にfalse…

原因

github.com

ワークアラウンド

なんと公式ドキュメントのサンプルにまで書いてあります…直さないのかな…

facebook.github.io

NetInfo.isConnected.fetch().then(isConnected => {
  console.log('First, is ' + (isConnected ? 'online' : 'offline'));
});
function handleFirstConnectivityChange(isConnected) {
  console.log('Then, is ' + (isConnected ? 'online' : 'offline'));
  NetInfo.isConnected.removeEventListener(
    'change',
    handleFirstConnectivityChange
  );
}
NetInfo.isConnected.addEventListener(
  'change',
  handleFirstConnectivityChange
);

地味にめんどい…

そもそもiOSのfetchAPIがネットがつながってないときにエラー返さないのが問題なんだけど…

【追記】 fetchにもtimeout設定されてた。

iOSのみ、ModalとAlertを同時に使うとModalが消えないバグがある

issue上がってます

github.com

再現方法

例えば通信処理中にインジケータ(くるくるするやつ)を出して、通信終わったらインジケータ消してレスポンスに応じてダイアログでユーザに通知したいとかあるじゃないですか。

このインジケータの表示にModalを使って通知にAlertIOSを使ったりすると起きます。

原因

Modalはshow/hide時はアニメーションを付加することができるのですが、このアニメーションの終了前にAlertを追加してしまうのが原因のようです。これは困った。

ワークアラウンド

いくつか上がってます

Modalのアニメーションを切る

一番変更の少ない方法です。上手く行くことを確認しました。 ProgressViewコンポーネントを作ってしまっていて、animationプロパティを変更するだけだったのでこれでとりあえず対応してます。 ただ、やっぱ見た目が悪い…

setTimeoutで終了を待つ

これはちょっと…。変更の手間が多いですね。

DroidKaigiに出席した

3月9,10日に新宿で開催されたDroidKaigi2017に参加してきました。

スピーカーでは無いです。

スピーカーの皆様、スタッフの皆様、超お疲れ様でした。

あの規模のイベント回すのはとても大変だし準備も相当大変だっただろうとお察しします。

個人的にも大満足のイベントでした。

2日間座りっぱなしで話聞くのって体力いるなって思いました、鍛えます。エンジニアって体力いるのね。

(訳あって1日目終わった後に御茶ノ水から西新宿まで1時間30分歩いたのが一番キツかったです

元々は全然行く気無かったんですが、仕事仲間が行くって話をCEOが聞いて「行かないの?経費で行ってきていいよ」って行ってくれたので行きました。ありがとうございました。

2月末でそもそも売り切れてるんじゃないかなーと思ってみてみたらちょうど増員したあとで参加出来ました。

基本的に私は勉強会に対しては

  1. 登壇者として行き、自分を知ってもらう。外との繋がりを作る
  2. 自分が日頃アンテナを張ってる情報やトライしている事に対する答え合わせ

という考えがあり、今回は2が目的で行ってきました。

今回に関してはDroidKaigiが日本で一番大きいAndroidアプリ開発者向けのカンファレンスということで

雰囲気を感じてみたいと言うのが一番でしたが。

聞いたセッションを個別に感想とか書くとすごく長くなるので省略しますが、

2日間終わってみて答え合わせ的には70〜80点位でした。

アンテナ張ってる領域に話は結構自分がわかる話も多く、張っているアンテナや自分の行動が

あまりズレていないということがわかり少しホッとしました。

これで全然分からない話ばかりだったらかなり自信無くしてましたね(笑

結構ニッチな領域だったりの話は分からない事が多かったのですが、聞いてて思ったのはやはり業務で使ってみてどうだったかという実践を踏まえた話ばかりだったということです。

「業務では使えないけど、興味あるから調べてみよう」

というのは(私の経験上)結構時間をムダにすることが多く、業務で使える領域を調べるか業務で使える領域を拡大していくかがやはり重要だなぁとしみじみ感じました。

やっぱ業務時間使ったほうが時間使えるし集中できるし責任もあるし成長早いよね。

何はともあれDroidKaigi楽しかったです。

来年は発表者になれたら行きます。

DroidKaigi関係無いけど宿をAirbnbで取ったのですが、最悪でした。ビジネスホテルの方がマシでした。

ReactNative製アプリをデバッグするときのTips

実機デバッグはVysorを使おう

chrome.google.com

実機の画面をキャプチャしてPCに転送してくれます。

キャプチャ画面でクリックやスクロール、タイピングもできるので非常に便利です。

ReactNativeのRELOAD機能はR2回押しで実機だと出来ないのでコレを使うと非常に便利です。

実機でデバッグオプションを表示する

デバッグオプションはオプションキーを押すと出てきますが、最近の端末はオプションキーがありません。

なので、adbを使ってオプションキーイベントを送ると表示されます。

 adb shell input keyevent KEYCODE_MENU

【追記】 なお端末をシェイクしても出るようです。

f:id:saburesan:20170228105827p:plain

基本はChromeコンソール、重いようだったらlogcatで

chromeコンソールデバッグはオブジェクトの中身がプルダウンで見ることができるので非常に便利です。 しかしながら非常に重かったり、そのせいで動作がカクつくことがあります。 chromeコンソールデバッグで無くてもよいのであれば、adb logcatを使いましょう

adb logcat | grep React

ReactでgrepするとReactNativeのログのみ見ることが出来ます。

react-native run-XXXがうまくいかないことがある

ReactNativeのプロセスが複数立ち上がっていたりするのでpsコマンドで確認して不要なプロセスはkillしましょう

ps aux | grep React

ReactNativeのソースを読んで見る(Android編①)

*Android編①って書いてるけどAndroid編②があるとかiOS編があるとかは未定です

ReactNativeの「Native」の部分がどう動いてるのか気になったのでソース見てみました。 細かい所まで見てないですし、間違った認識かもしれませんのであしからず。

エントリポイントはMainActivity

AndroidManifestにはMainActivityとDevSettingsActivityでaction.MainがMainActivityでここからアプリがスタートするようです。 DevSettingsActivityはデバッグ用のメニュー(ReloadやLiveReloadなどのメニュー)を表示するためのアクティビティのようです。

MainActivity

ReactActivityというAbstractClassを継承したクラスで自動でgetMainComponentNameがオーバライドされてプロジェクト名を返すようになっています。

ReactActivity

Activityを継承しDefaultHardwareBackBtnHandler, PermissionAwareActivityを実装したクラスです。 基本的にはActivityでしか受け取れないイベント(ライフサイクルやonActivityResultなど)が起きたときにReactActivityDelegateに伝播するクラスです。DefaultHardwareBackBtnHandlerはJSModuleから呼び出されonBackPressedを実行するinvokeDefaultOnBackPressedのみをインターフェースとして持っており、PermissionAwareActivityはActivityのパーミッション関連のメソッドと全く同じメソッドが定義されたインターフェースです。 ちなみにデフォルトで生成されるMainActivityはandroid.app.Activityを継承していますが android.support.v4.app.FragmentActivityを継承したReactFragmentActivityも用意してあります。サポートパッケージを使う場合はこちらに変更するようです。

ReactActivityDelegate

このクラスも基本的にはReactActivityから伝播されたイベントをReactInstanceManagerに伝播しています。 デバッグ時のメニュー表示に必要なパーミッションの処理もこのクラスで処理されています。 onCreateでloadAppメソッドでReactRootViewのインスタンスが生成され、startReactApplicationを実行した後setContentViewされます。

ReactRootView

このクラスはSizeMonitoringFrameLayoutを継承しRootViewを実装しています。 SizeMonitoringFrameLayoutはonSizeChanged発生時にリスナを発火してくれるだけのFrameLayoutのサブクラスです。 RootViewは子ViewにMotionイベントが発生した時に呼ばれるonChildStartedNativeGestureだけを持つインターフェースです。 ReactRootViewは基本的にタッチイベントやViewの表示領域の変更(回転やキーボードの有無)をJS側へ通知する処理をしています。 onMeasureが呼ばれ描画領域が確定するとattachToReactInstanceManagerが呼ばれReactInstanceManagerのインスタンスメソッドattachMeasuredRootViewが呼ばれReactRootViewが渡されます。 デフォルトではReactInstanceManagerはXReactInstanceManagerImplのインスタンスがセットされます。

XReactInstanceManagerImpl

このクラスはまだ全然理解出来てません。 setupReactContextでReactRootViewの初期化を行っています。 XReactInstanceManagerImplはReactRootViewの配列をフィールドに持っており、個々の初期化はattachMeasuredRootViewToInstanceで行われています。 attachMeasuredRootViewToInstanceはReactRootViewとCatalystInstanceを引数に持ち、CatalystInstanceクラスはJS<->JavaAPI呼び出しをする環境を提供してくれるらしいです。

UIManagerModule

catalystInstance.getNativeModule(UIManagerModule.class)でUIManagerModuleのインスタンスを取得してます。このクラスはReactContextBaseJavaModuleを継承しており、これはJS側からJavaのコードを呼ぶために必要なものです。自分でJavaからAPIを呼びたい時はこのクラスのサブクラスをつくります。 UIManagerModuleはJSのComponentからイベントを受け取り、フィールドのUIImplementationのインスタンスに伝播します。 UIManagerModuleのaddMeasuredRootViewメソッドでは引数のReactRootViewをフィールドのUIImplementationに登録しています。

UIImplementation

最終的にコイツがReactRootViewを持ち、JSのComponentからイベント受け取って描画すると思われる(この実装は今後読む

ReactNative雑感

最近自社のアプリ(iOS,Swift/Android, Kotlin)をReactNative化しているので使ってみた雑感など。

導入のきっかけ

自社アプリのiOS(Swift)とAndroid(Kotlin)は私が一人で作って運用しています。

なんで一人かというと社内でアプリをかけるのは私しかいないからです。

そんなわけで、新しい機能の追加や修正などは2つのプロジェクトを触らなければならなくて非常に手間がかかるんですよ。

そんなわけでちょっと開発が落ち着いたのを見計らって巷で話題(?)のReactNativeを調べ始めたのがきっかけです。

ちなみにJavascriptはほぼ初心者です。

調査段階でのメリット

  • iOS, Androidでコードの共通化ができる(100%ではない)
  • iOS, Androidのネイティブコードを呼び出すことができる(既存のコードを再利用できる?)
  • RectをベースにしたJavascriptとJSXスタイル
  • レイアウトがHTML, CSSスタイルで記述でき、Flexboxスタイルで画面対応ができる
  • HotReload, LiveReload機能がありコードの変更が即時反映される(XcodeやAndroidStudioのビルド待ちが無い)
  • Javascriptが勉強したいんじゃ(個人の気持ちです

調査段階でのデメリット

  • まだベータ版
  • 情報が少ない
  • 日本での人気をあまり感じない
  • JavascriptもReactも初めて(個人の問題です

実際に触ってみて

書き始めてまだ4日くらいですが、基本的な部分は一通り試しました。

調査段階のメリットデメリットとの差分を少し書きます。

調査段階でのメリットについて

iOS, Androidでコードの共通化ができる(100%ではない)

かなりの部分共通化出来ました。 今のところそこまでネイティブでViewを分ける必要は無いのでネイティブのダイアログを読んだりする程度に留めてます。

iOS, Androidのネイティブコードを呼び出すことができる

出来ました。 公式のリファレンスはObj-cとJavaでしたが、SwiftとKotlinでも問題無く呼ぶことが出来ました。 ただXcodeがObj-c <-> Siwftのブリッジの設定を上手くやってくれなくてちょっとハマりました。

RectをベースにしたJavascriptとJSXスタイル。レイアウトがHTML, CSSスタイルで記述でき、Flexboxスタイルで画面対応ができる

これはめっちゃ良かったです。 AndroidStudioのXmlエディタがすごく優秀なのにくらべ、XcodeのStoryboardはめっちゃ使いづらくSnapKitを使ってコードでView書いてたのですがビルドしないと見れないし色々と辛みがありました。 HTML、CSSの知識があれば問題無く書ける感じで非常に良かったです。

HotReload, LiveReload機能がありコードの変更が即時反映される(XcodeやAndroidStudioのビルド待ちが無い)

これもいいですね。実機でも動くのでデバッグがかなり楽です。 AndroidとiOS2つも起動するとLiveReloadは1つだけしか出来ませんが、HotReloadはどちらも使えるのでデバッグが同時にできるのも非常に良かったです。 ただ、まだ結構バグが多くて上手く行かないことも多かったです。

Javascriptが勉強したいんじゃ(個人の気持ちです

勉強になりました。 ReactNativeはES6で書けるのでそっちの勉強にはなります。

ちなみに本を2冊読んでからはじめました。

www.oreilly.co.jp

www.oreilly.co.jp

調査段階のデメリットについて

まだベータ版

ですね。まだまだバグが多いです。 ハマった時になかなか正解に行き着かないのでそういう時間は結構かかりました。 結構デバッグが大変で、Chromeデバッグログの確認とかできるのですがこれをするとめちゃくちゃ重くなります。 iOSChrome使ってますが、Androidadb logcat | grep Reactで見てます。

情報が少ない

公式リファレンス通りに動かないこともあります。

日本での人気をあまり感じない

TweetDeckでKotlinとReactNative垂れ流してるけどKotlinの日本人ツイートめっちゃ多いのにReactNativeは超少ない。 ちょいちょいブログとかで記事見るけどあんまり好印象の記事見ない…

JavascriptもReactも初めて(個人の問題です

大変でした。 とは言っても2日も書いていればなれます。 設計はFluxを使ってるんですが、これもよくわからなくて azuさんの神資料をもとに実装したらなんか理解した感じになったのでよかったです(?)

10分で実装するFlux

感想

全然まとまりないけど雑記だからね!! 両OSを1ソースで書けるけど、今はそこまで開発速度出てないのでネイティブの方が早そう… まぁでも上手くやれば運用は楽かもしれない。 ビルド待ちが無い分開発のストレスは少ない。 以前はてブで話題だった「もうNativeOnly開発者はいらないのかもしれない」というタイトルの記事については、うーん触った感じは真逆な印象。 ぶっちゃけReactNative使ってもちゃんとしたアプリ作りたいならUIKit, AndroidSDKの知識は必須。あとネイティブのAPIも普通に使えなきゃいけないからSwift, Obj-c, Java, Kotlinの知識とかも必要になってくる。 要はチーム編成の問題で、iOSアプリ, Androidアプリ担当がちゃんといるんならそれで済むし、ReactNativeでロジック部分共通化したいってなればiOSネイティブ部分担当とAndroidネイティブ部分担当とReactNativeロジック担当みたいな構成になるのかな(適当

まぁ銀の弾丸は無いってことで。

http://www.jp.square-enix.com/magazine/gansma/gangan/b_ichi/img/comics-03.jpg

B壱 | ガンスマ情報局 | SQUARE ENIX

(ソウルイーター超人気出たけどコイツ出なかったなぁ…)

Kotlin Android Extensionsで注意すること

f:id:saburesan:20160906091652j:plain

とくにハマった訳ではないですがやらかしそうなミスだなぁと思ったので。

Kotlin Android ExtensinsとはViewの参照を自動で作ってくれるAndroidのためのPluginです。

kotlinlang.org

findViewByIdしなくてもID名から自動で参照を作ってくれるので非常に便利です。

f:id:saburesan:20170214104202p:plain

使い方

app以下のbuild.gradleに

apply plugin: 'kotlin-android-extensions'

を追加するだけ。

仕組み

Kotlin側ではR.id.xxxのxxxでViewへアクセスできるので、thisがViewクラスまたはサブクラスの場合、そのViewに対してxxxのIDをfindViewByIdをしているだけです。

Javaデコンパイルするとこんな感じ

ViewPager var7 = (ViewPager)this.findViewById(id.viewPager);

毎回findViewByIdするのはパフォーマンス悪いんじゃないかなーと思ったらキャッシュする機構がちゃんとありました。

...
 private HashMap _$_findViewCache;
...
 public View _$_findCachedViewById(int var1) {
      if(this._$_findViewCache == null) {
         this._$_findViewCache = new HashMap();
      }

      View var2 = (View)this._$_findViewCache.get(Integer.valueOf(var1));
      if(var2 == null) {
         var2 = this.findViewById(var1);
         this._$_findViewCache.put(Integer.valueOf(var1), var2);
      }

      return var2;
   }

   public void _$_clearFindViewByIdCache() {
      if(this._$_findViewCache != null) {
         this._$_findViewCache.clear();
      }

   }
...

この機構ですが、ActivityまたはFragment内でしか使われていないようです。 Viewのサブクラス内だと現状毎回findViewByIdしてるっぽくて、あんまりパフォーマンス良くないようです。 (Fragment無しのViewのサブクラスベースで作ってて後から気づいた…

注意点

インポートに注意

Kotlin Android Extensionsを使うとレイアウトファイルのViewのIDを元に参照できるようにkotlinx.android.synthetic.main.<レイアウト名>.*をインポートしなければいけません。 この時に、複数のレイアウトファイルで同じIDを使っている場合にインポート候補が複数出ます。 もし、間違ったレイアウトをインポートしても静的解析ではエラーは検出出来ません。 仕組みで紹介したようにthisに対してfindViewByIdしているのでViewが無ければヌルポです。

Viewの中でthisが変わった時に注意

Viewのサブクラス内でインナークラスを定義したり、apply, runなどを使ってthisが変わる場合も注意が必要です。 私はapplyで初期値の設定などを行うのですが、

viewPager.apply {
            adapter = pagerAdapter
}
tabLayout.apply {
            addTab(newTab().setText(R.string.item_state_a))
            addTab(newTab().setText(R.string.item_state_b))
            addTab(newTab().setText(R.string.item_state_c))
            viewPager.currentItem = 1
        }

こう書くとonTabSelectedのviewpagerがヌルポで死にます。 applyに渡されたブロック内ではthisがレシーバになるので例ではtabLayoutからviewPagerを探そうとしてヌルポになります。

まとめ

気をつけていればあまり起こることは無いのかもしれないです。 インポートのミスについては気をつけるしかないのですが、 Viewのサブクラス内ではキャッシュされないであるとかthisが変わる際にミスが起きやすいなどの対策としては インスタンス変数として保持するようにするのが一番効果的だと思います。

private val viewPager by lazy { view_pager } //view_pagerはKotlin Android Extensionsの参照

KotlinからJavaのクラスを利用するときにAccessorが上手く生成されないことがある

f:id:saburesan:20160906091652j:plain

Kotlin小ネタなんですが、KotlinからJavaのクラスを利用するときにjavaのSetter, GetterメソッドからKotlinのAccecorに自動で変換してくれる機能があります。

例えば、 Java側で

class Text {
...
public void setText(String text) {...}

public String getText() {...}
}

このようなTextクラスをKotlin側で利用する場合

val text = Text()
text.text = "hoge" //setter
text.text              //getter "hoge"が返る

このように使うことが出来ます。

基本的には非常に便利なのですが、Java側のSetter/Getterの定義の仕方で上手く動作しない場合があるようです。

AndroidのEditTextクラスで起きました。

EditTextクラスにはEditable getText(), _setText(Charsequence c)というメソッドがあります。

Setter/Getterで型が違うのですがKotlin側ではtextアクセサが生成されます。

補完にもsetText, getTextは無いです。

f:id:saburesan:20170212233107p:plain

そして型はEditableになっています。

型がEditableになっているのでtextアクセサ経由でStringクラスをセット出来なくなります!!!

AndroidStudio上だとこのようにエラーになります。

f:id:saburesan:20170212231444p:plain

画像見てもらうと分かるように、setText(“”)と書いている部分は警告も何も出ていません。

Kotlinのアクセサが使える場合にJavaのSetter/Getterを書くと画像のgetText()のように黄色い波線で警告が出ます。

f:id:saburesan:20170212231859p:plain

しかし、setText(“”)の方は何も出ていません。

ということはKotlinが生成したtextアクセサのSetterはsetText()と別物と解釈されているということです。

でもtextアクセサとsetTextの定義を見てみるとどちらも同じTextView#setText(Charsequence text)を参照しています。

試しにKotlinバイトコードデコンパイルしてJavaのコードを見てみると…

Kotlin

f:id:saburesan:20170212232717p:plain

Java

f:id:saburesan:20170212232749p:plain

やはり同じでしたね。

わざわざEditableのサブクラスに変換したのにCharsequenceにキャストすんのかい。

とは言え、setText(Editable)という存在しないメソッドがKotlin側で定義されちゃってるようですね。

これはバグなんですかね。

DIY子供の絵本棚

f:id:saburesan:20170130075220j:plain

設計

Sketchを使って作りました。

f:id:saburesan:20170130075446p:plain

よくある形です。

本棚は3段にして、下はおもちゃ箱です。 扉が開閉します。重いので1歳のうちの子は開けることができないです。

近くのナフコに89mm * 19mm * 183mmの木材が200円で売ってたのでそれで作れる範囲にしました。 また、お得コーナーに60cmカットされてたのが17本あったのでそれも買いました。カット代節約。

木材カットはナフコで1回20円でやってくれます。 183cmから60cmを3本の場合3回必要です。 これを2本やるのですが単純に倍になるわけでは無く、カットする機械に乗れば1回で済むので2本でも3回で済みます。

とか考えてカットする木材の長さは少ないほうがカット代もかからないので、今回の設計では木材は2パターンの長さしか無いです。

工程

写真は撮ってないので無いです。

金額

3000円位。

期間

1日位

感想

設計してからだったのであとは作業でした。

ただ、安い木を使ったのもあって微妙な曲がり具合で調整が必要な箇所が結構ありました。

最終的にいい感じに仕上がって良かったです(適当

子供はなんか珍しそうに見てて、1段目の本を2段目に、2段目の本を3段目に置いて遊んでました。

大人にとってはなんでもない事ですが、子供にとってはそういうことも初めてのことで楽しいんでしょうね。

子供の視点って面白い。

何はともあれ気にってくれてるようで良かった。

f:id:saburesan:20170130142527j:plain