Kotlin 1.1以降をAndroidで安全に使いたかった話
はじめに
この記事は「Kotlin 1.1以降をAndroidで安全に使いたかった話」です。本当は会社のAdvent Calendar向けに書くはずだったのですが、色々あってお蔵入りになったのでこちらで供養。 ちなみにこれはボツネタ第2弾で、第1弾はこちら。 muumuutech.hatenablog.com
Kotlin 1.1 とJava 8
Kotlin 1.1がリリースされて久しいが、1.1.系からJava 8 に依存したAPIがいくつか追加された。AndroidはAPI Level 24からJava 8 に対応しているので、23以下の端末でうっかり該当APIが実行されるとCrashしてしまう。
Java 8 に依存したAPIは@PlatformDependent
アノテーションが付与されている。今の所このアノテーションが付与されているのは、Map
インターフェイスのgetOrDefault() とMutableMap
インターフェイスのremove()の二つ。
これらのAPIを安全に扱うためにCustom Lintの導入を試みた。1
Custom Lintを作ろう
Android LintもしくはJava Lintに使えそうなものがないか確認したところ2なさそうなので作っていく。gradleの設定をして、Detector, Issue, Registryの順に作っていく。
最初にGradleでビルドするプロジェクトを作る。言語は今回Kotlinにした。
Gradle
プロジェクトができたらGradleを書いて行く。
lint-api
とlint-checks
を追加する。(kotlinを使う場合はkotlin用の依存も追加)
dependencies { def lintVersion = "25.3.0" compile "com.android.tools.lint:lint-api:$lintVersion" compile "com.android.tools.lint:lint-checks:$lintVersion" }
そしてここがポイントなのだが、Lint-Registry-v2 でRegistoryを定義する。色々ググったらLint-Registry
で定義している情報が出てくるが、2017年末時点ではLint-Registry-v2
で定義しないとLintがうまく動かなかった。(これに気がつかず何時間も無駄にしてしまった…)
jar { manifest { attributes("Lint-Registry-v2": "com.your.package.CustomLintRegistry") } }
Detector
今回やりたいことは「特定のアノテーション(@PlatformDependent
)が付与されたAPIを使用した際にwarningをだす」である。本来であればJavaPsiScanner
を使ってASTに解析されたノードを探索して行って…みたいなことをするべきなんだろうけど、Advent Calendar向けにはヘビィだったので妥協案でいくことにする。
該当APIが付与されたAPIはたったの2個なので、「それらが使われた時にwarningをだす」でとりあえずお茶を濁すことにする。特定のコードが記述されているか検知したいので(まずはよく使うgetOrDefault()
でlintチェックが出るか試す)、 ClassScanner
インターフェイスを使い、必要なメソッドをoverrideしていく。
getApplicableCallNames()
detectorが検知したいメソッド名のリストを返すメソッド。今回は特定のメソッドがコールされたことを検知したいので、そのメソッド名をstringのmutable listにして返してやる。
override fun getApplicableCallNames(): MutableList<String> = mutableListOf("getOrDefault")
checkCall()
detectorが上記のgetApplicableCallNames()
で定義したメソッドを検知すると呼ばれるメソッド。本当はpackageのチェックとかやらなきゃいけないがとりあえずノーチェックで通す。
override fun checkCall(context: ClassContext?, classNode: ClassNode?, method: MethodNode?, call: MethodInsnNode?) { context?.report(ISSUE, method, call, context.getLocation(call), "Don't use Java 8 dependency API") }
Issue
DetectorクラスのstaticなオブジェクトとしてIssue.create()
メソッドを使って作成する。
適当に各パラメータを設定しよう。Implementation
の第1引数には上記で作成したDetectorのクラスオブジェクトを渡す。
class CustomLintDetector : Detector(), Detector.ClassScanner { companion object { val ISSUE: Issue = Issue.create( "Java8Api", "Java 8 API is used", "Java 8 API is used. This causes crash with Android OS level under 23.", Category.CORRECTNESS, 6, Severity.WARNING, Implementation(CustomLintDetector::class.java, Scope.CLASS_FILE_SCOPE)) }
Custom Lintを実行する
ここまでで作ったプロジェクトをbuildして作成されたjarファイルを.android/lint/
配下に格納し、./gradlew lint
コマンドを叩けばlintが動く。
ここからが本当にわけがわからないのだが、fat jarにしないとうまく動いたり動かなかったりする。(自分で書いていて「そんなことある?」って今思ってます…)
jarファイルを作る環境のJavaのバージョンだったり、gradleのバージョンだったりをいくつか試してみたが、どういう時に動いてどういう時に動かなかったか全然切り分けができなかった。Gradleのcacheを疑ったがこれもダメ。
そう行ったわけでQiitaで書いているAdvent Calendarに載せるわけには行かなかったのでこちらで供養。なんだかモヤモヤしたままだが、とりあえずfat jarにしておけばこんな感じでCustom Lintを作ることはできた。
感想
Custom ListをよりによってKotlinで作ろうとして一番大変だったのは、とにかくドキュメントがないこと。公式もそうだが、全体的に情報が少ない。ヒットしても数年前のものだったりして現状で動かなくなったりして困った。無駄にCustom Listの中のコードを読んでちょっと詳しくなってしまった気がする…
もしもどこかに体系化された素晴らしいドキュメントがあれば教えてください(´;ω;`)
Links
実装にあたりこれらの記事を参考にした。
Help developers with custom Lint rules · Jeremie Martinez
www.slideshare.net
-
Jet Brains 公式ドキュメントでは
kotlin-stdlib-jre7
とkotlin-stdlib-jre8
を使えと書いている。kotlin-stdlib-jre7
だけを指定するとJava 8 に依存した機能を使った時にコンパイルエラー出してくれるかな?と期待したが、残念ながらエラーは出なかった。kotlin-stdlib-jre8
を指定しようと思ったらAndroidではJack and Jill
へ依存を持っているようでgradleのsyncすらできなかった。(Jack and Jill
はdeprecatedになりましたね…)↩ -
むしろデフォルトでチェックがついているJava 8 migration aidsの
Replace with single Map method
でMap.getOrDefault()を使うように推奨してきます😇↩
2017年を振り返る
はじめに
2017年に自分が何をしていたか後から振り返られるようにメモを残しておく。 shirajiさんのエゴサで振り返るというアイディアがとても素敵だったので彼に倣ってtweetをペタペタしていこう。
1月
🌏❤🍻㊗️🌏㊗️🍻❤🌏
— むーむー (@muumuumuumuu) 2017年1月19日
自分が業務でやっているサービスのAndroid Appが初の海外対応をリリースして浮かれていた。この時に割と多言語・他地域対応の知見が溜まったのでよかった。
ここでちょっと業務が落ち着いたので以降はDroidKaigiの準備に追われていたように思う。
2月
android-jpのslack channelにjoinしてみた。知っている顔がちらほらあって安心する。
— むーむー (@muumuumuumuu) 2017年2月3日
今まで怖くて入れなかったandroid-jpのslack channelに入ってみた。最近は #generalより #english の方が活発な不思議なコミュニティだった。
けものフレンズ、twitterでよくわからないけど流行ってるなーくらいの認識だったけど、4話で豹変すると聞いてうっかり手を出してしまった。あれは我々の倫理観を揺さぶるやばいやつだぞ…
— むーむー (@muumuumuumuu) 2017年2月13日
そうか、けもふれは今年の2月か…
皇居ランデビューして10km女子の部で2位になったので副賞にお米貰った。重い。嬉しいけど、重い。
— むーむー (@muumuumuumuu) 2017年2月19日
この頃からランニングを再開していたようだ
3月
DroidKaigi最高に楽しかった!いろんな人から直接セッションのフィードバックもらえたし、本当に貴重な体験でした!来年も出来ればスピーカーで出たい! #droidkaigi
— むーむー (@muumuumuumuu) 2017年3月10日
DroidKaigiにスピーカーとして参加した。最高に楽しかったけど、今年はrejectされたので聞く側で楽しんできます :p この頃はずっとねこ、えさ、ねこ、って言ってたきがする
DroidKaigi2017で登壇した時のvideoが公開されました!🎉🎉🎉🎉
— むーむー (@muumuumuumuu) 2017年8月15日
https://t.co/btQdU1c2Um
- 作者: スティーブ・マクラッチー,花塚恵
- 出版社/メーカー: ダイヤモンド社
- 発売日: 2015/04/17
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (1件) を見る
4月
この頃からPodcastを大量に聴き始める。そういう意味で今年はインプットの方法が大きく変わった。通勤中とかもうほとんど音楽聞かずにPodcastばかり聴いている。最近たまたま見つけた @engineermeeting のpodcastがすごく面白い。ちょっと自分より上の年代の人たちが話しているんだけど気づきが多い気がする。話がぽんぽん飛んでいくのが新鮮で良い🙂
— むーむー (@muumuumuumuu) 2017年4月4日
Androidを支える技術〈I〉──60fpsを達成するモダンなGUIシステム (WEB+DB PRESS plus)
- 作者: 有野和真
- 出版社/メーカー: 技術評論社
- 発売日: 2017/02/22
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (2件) を見る
5月
ビッグウェーブに乗って書いた٩( 'ω' )و We ♡ Kotlin!
— むーむー (@muumuumuumuu) 2017年5月18日
RettyとKotlinの歩み〜アプリからサーバサイドまで - rettydev’s blog https://t.co/tYvMQkMadq
KotlinがAndroid公式サポートされた。便乗して会社のTech Blogを書いていた。
引っ越し完了祝いに最高に美味しい赤身肉と最高に美味しいクラフトビールのお店に行ってきた。日曜だからか空いててかなり穴場だったのでマジでおすすめ!最高に美味しいお肉の脂は官能的だってみんなに知って欲しい…!https://t.co/HKIna8jyOI pic.twitter.com/1Vgmr38ub3
— むーむー (@muumuumuumuu) 2017年5月28日
自宅の引越しを行ったのもこの時期。都民デビュー。
6月
五反田最終日。お世話になりました。
— むーむー (@muumuumuumuu) 2017年6月23日
麻布十番オフィス初出勤
— むーむー (@muumuumuumuu) 2017年6月26日
今度は自宅ではなくオフィスも引越した。
とだか貸切最高〜〜〜 pic.twitter.com/HNwU0p0jFV
— むーむー (@muumuumuumuu) 2017年6月14日
引越し直前にオフィス近くの人気店とだかを貸し切って美味しい食事をしていた。
今日は代休。諸々所用が終わって夕方から一部のAndroidエンジニア大人気で気になっていたSHIROBAKOをamazon primeでみている。まだ2話しか見ていないけどこれはPMの話だ…納期とクオリティとプロジェクト進行。
— むーむー (@muumuumuumuu) 2017年6月29日
SHIROBAKOに手を出し始めたのもこの頃。
7月
Swiftに手を出し始めたようだ。Swift入門するはずが築地デート満喫していた
— むーむー (@muumuumuumuu) 2017年7月29日
アラサーにして初めてまともにドラクエをみた。夫がPS4版ドラクエ11をやってるのをところどころチラ見していたけど衝撃のストーリー展開にびっくりしすぎて自分でもやり始めた。セーニャちゃんと結婚したかった…ドラクエやってる夫「魔物が出てきた時点で村長に報告に行くべきだよね。魔物出てきたんですけど先進んじゃって本当に大丈夫ですかーって。」
— むーむー (@muumuumuumuu) 2017年7月29日
8月
Lisp本を読み切った。なんだかんだネタ枠と言いつつ意外とよかった…ネタです。みんなで関数型プログラミングやろうぜ〜〜〜 /
— むーむー (@muumuumuumuu) 2017年8月1日
Kotlin初心者から抜け出したい?それなら、 - 言いたいことはそれだけかhttps://t.co/wrnnWsZvkQ pic.twitter.com/bgpz2856Hn
会社でやっているもくもく会が20回を突破。 もくもく会については最近ポエムも書いた。[リンク修正しました] 第20回 Androidもくもく勉強会@ Rettyオフィス を公開しました! 8/23(水) 開催です。ついに20回突破! #AndroidMokuMokuRettyhttps://t.co/c5QBRMSroM
— むーむー (@muumuumuumuu) 2017年8月8日
Swift入門枠で登壇もやった。今日の発表のスライドあげました。「KotlinエンジニアがSwiftに入門したらちょっと戸惑った話」です。https://t.co/aTaoEVQmsG #swift_kotlin_jp
— むーむー (@muumuumuumuu) 2017年8月9日
Fearless Change アジャイルに効く アイデアを組織に広めるための48のパターン
- 作者: Mary Lynn Manns,Linda Rising,川口恭伸,木村卓央,高江洲睦,高橋一貴,中込大祐,安井力,山口鉄平,角征典
- 出版社/メーカー: 丸善出版
- 発売日: 2014/01/30
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (16件) を見る
9月
今からこの話します! https://t.co/5Q5d7DALRK #Retty_tech_cafe
— むーむー (@muumuumuumuu) 2017年9月1日
KotlinとReduxの話を自社イベントでしてきた。
Android エンジニアTシャツ、この日以降まだ出番はない。
Androidエンジニアです!!!! #Retty_tech_cafe pic.twitter.com/9aGhz0FW65
— むーむー (@muumuumuumuu) 2017年9月1日
着替えるの面倒だったのでAndroidエンジニアTシャツ着たまま電車乗って帰宅したけれど、誰からも何も言われないし特に視線も感じなかったのは華金だからなのか
— むーむー (@muumuumuumuu) 2017年9月1日
10月
この頃からTwitterをはじめ色々なwebサービスで設定しているアイコンを自分の顔写真にした。ちょっと前にアイコン変えたけど、私だって認識されてない気がしてきた。
— むーむー (@muumuumuumuu) 2017年10月23日
11月
DroidKaigi不採択だった〜〜〜😇😇😇😇
— むーむー (@muumuumuumuu) 2017年11月6日
DroidKaigi 2018のCfPが不採択でショックを受けるなか、その日は外部の勉強会でKotlinとサービスについて話してきた。今からこの話します! https://t.co/oG2Fs5uxdp #yjbonfire
— むーむー (@muumuumuumuu) 2017年11月6日
ハーフマラソンを走った。20km以上走るのは7年前のフルマラソン以来だったので不安だったが無事完走。ハーフ完走したぞ!イェーイ!!🎉🎉
— むーむー (@muumuumuumuu) 2017年11月12日
ずっとファンだったPodcastにゲストとして呼んでもらった。感動した。念願の @dexfmpodcast デビュー🎉 @shiraj_i さんのエピソード期待してた人達ごめんなさい 🙏 (実はその前に収録してました) https://t.co/ueKFSeUt5d
— むーむー (@muumuumuumuu) 2017年11月27日
12月
社内のエンジニア向けに「Androidではじめの方に知っておきたかったこと講習会」みたいなことをやり始めた。これは機会があれば資料を綺麗に作り直してどこかに出したい。今日社内でAndroid興味あるエンジニア向けに簡単なAndroid講習会やったんだけど、ActivityとContextとIntent周りを軽く話したら「Androidつらそう…」と言われてしまったので次回は楽しい話をしよう。
— むーむー (@muumuumuumuu) 2017年12月19日
仲間内でクローズドなPodcastごっこを始めた。最近仲間内のエンジニアでPodcastごっこをしているのだけど、private sharingできるaudio serviceないのかなー。Show note とかも合わせて表示できる感じの。
— むーむー (@muumuumuumuu) 2017年12月25日
来年
DroidKaigi 2018のセッションの解説をする。プレミアムフライデーって本当に実在するのか…。解説陣がgfxさんshirajiさんと(私はともかく)豪華なので楽しいと思うのでぜひご参加ください。 connpass.com私はDroidKaigiリジェクトされた勢ですがセッションの解説にお招きいただいたので、リジェクトされた人もそもそもCfP出してない人もお気軽にどうぞ♥ https://t.co/pMFVcVU2V2
— むーむー (@muumuumuumuu) 2017年12月5日
おまけ
夫が可愛いシリーズ
夫「K(かっこいい)P(パパ)T(強い)」
— むーむー (@muumuumuumuu) 2017年1月24日
夫「Android Oって何になるの?…おかし?」
— むーむー (@muumuumuumuu) 2017年3月21日
かわいい
夫の夕飯リクエストがピカタだったので準備してたら、本当に食べたかったのはミラノ風カツレツだということがお肉を焼いている段階で判明してしまった。なぜピカタというワードが夫の口から出てきたかは不明。それでも美味しいって言って食べてくれるから天使なのかもしれない😇
— むーむー (@muumuumuumuu) 2017年4月1日
ランニングから帰ってきたら旦那さんが「今日は6km?」という感じで距離を当ててくるようになった。これがかなりの精度で当たる。走ってきた時間とか私の疲れ具合とか気温とか天気とかの変数で予測するらしい。好きな人じゃなかったら怖いぞってレベルで当たる。
— むーむー (@muumuumuumuu) 2017年4月16日
今日は天気もいいし風も気持ちいいので、窓を開けてビールを飲みながら旦那さんと家庭内もくもく会してる。ビールはGRAND KIRIN DIP HOP WEIZEN BOCK! 🍺
— むーむー (@muumuumuumuu) 2017年4月16日
ハーフマラソン申し込んだ!完走賞でチョコレート貰える話を夫にしたら「そんなに頑張らなくてもチョコレートは手に入るんだよ?」と圧倒的正論を頂きました😇😇😇 https://t.co/U3DJ5gj5mN
— むーむー (@muumuumuumuu) 2017年9月2日
これは惚気なんですけど、今朝出社途中に深夜作業から帰宅する夫とばったり出会って、「好きな人と道端で偶然出会う」というシチュエーションをめちゃめちゃ久しぶりに体験してテンション上がった✌️
— むーむー (@muumuumuumuu) 2017年9月15日
KotlinJSでReact Nativeを書いてみたかった話
はじめに
この記事は「KotlinJSでReact Nativeを書いてみたかった話」です。つまりできなかった話 であることをあらかじめご了承ください。
では本題。
年末になり会社のAdvent Calendarのネタ探しをしている最中に電波が降りて来た。
KotlinでReact Nativeっていけるのかな。Kotlin 1.1 からJavaScriptサポートしてるからいける気もする。可能ならiOS Appも部分的にKotlinで開発できるのでは?
— むーむー (@muumuumuumuu) 2017年10月25日
当時Kotlinが好きすぎてちょっとおかしくなっていたのか、「SwiftではなくKotlinでiOSかけたら最高では?😇 」「でもKotlin/Native で書くのはちょっとまだ早すぎるよな〜〜😓」「あれ?KotlinJSでReactNativeいける!?!?」
ネタとしてもちょっとおもしろいかなと思いやってみることに。
ちなみにKotlin/Nativeまだ実務ではつらいよねって話は同僚がまとめてくれた記事が良いので興味ある人はぜひ読んでほしい。せめて補完が効くようになったら起こしてください( ˘ω˘ ) スヤァ…
React NativeとKotlinについて
React NativeはJavascriptで記述する。その名の通りReactベースなのだが、コンポーネント部分をそれぞれのプラットフォームのブリッジでごにょごにょしてくれるのでnativeとして動く。つまり、AndroidであればJavaに1、iOSであればSwiftのコードになる。2
一方Kotlinは1.1よりJavascriptへのコンパイルをサポートした。どういうことかというと、Kotlinで書いたコードがJavascriptにconvertされるということ。
これらを組み合わせて、
- React NativeでJavascript部分をまずはKotlinで書いて、
- それをJavascriptに変換し、
- さらにブリッジを経由してJavascriptが対応するSwiftもしくはJavaのコードとして動作
これにより実質KotlinでReactNativeでは?というのがやりたいことになる。
やってみた
まずはReact Nativeのプロジェクトを用意する
このへんをみて適当にプロジェクトを作る。まずはデフォルトで生成されるJavascriptがちゃんとアプリで表示されることを確認。デフォルトの画面が表示されえたのでOK。
JSのコードをKotlinに置き換えたかった
Kotlin JS がサポートしているビルド方法は下記4種類。
- Getting Started with Gradle
- Getting Started with IntelliJ IDEA
- Getting Started with Maven
- Getting Started with the Command Line
それぞれCompile後にjsファイルが吐き出される。
React NativeはReactで動いているので、まずはReactをimportする。 Kotlinを開発しているJetBrains社がWrapperを用意してくれているのでこいつをinstallする。
npm i @jetbrains/kotlin-react
次にReactNativeのコンポーネント部分をimportしよう、と思ったところでそんなものは世の中に存在しないことに気がついてしまった。
Kotlin JS でReact Nativeのcomponentが使えないことに気がついてしまった😇 Bridge使うのもやりたいことと違うんだよな… #AndroidMokuMokuRetty
— むーむー (@muumuumuumuu) 2017年11月30日
自分で書くにはJS力足りないし、何よりもはやAdvent Calendarのネタとしては重すぎるよな〜〜というわけでお蔵入りになりました :p
というわけでボツネタ供養でした :pray:
小規模もくもく会運営についての知見とポエム
この記事は勉強会運営 Advent Calendar 2017の19日目の記事です
はじめに
会社でもくもく会を運営しているが今年はついに20回を超え、ありがたいことに他社からも「御社のもくもく会を パクリ 参考にしてうちでももくもく会はじめました 」と言ってもらえることが増えた。勉強会運営としてはある程度いい感じに回っている気がするので誰かの参考になればと知見とポエムを書き残しておきたいと思う。
注意事項
上記で「いい感じに回っている」と記載したが、それは健全な勉強会コミュニティを形成すると言う意味であり、採用目的(「いいエンジニアをシュッと採用したい」など)の観点ですぐに効果が出ることを期待しているのであれば、おそらくこの記事はあなたの求めているものではない。
「Androidもくもく会@Rettyオフィス」の歴史
私が運営しているのは「Androidもくもく会@Rettyオフィス」というAndroidエンジニアを対象としたもくもく会である。この勉強会はもうすぐ2年ほど経つが、2年間の間に色々模索しながら形態を変えてきた。 形態によって大きく分けて3つの時期に分けられ、それぞれどのような学びがあったかをまとめたいと思う。
1. 「もくもく + 懇親会」期
そもそもこの勉強会自体は私がはじめたものではない。(第1回目は参加すらしていない。)もともと他社のエンジニアに弊社のことを知ってもらったり、業界のエンジニアと知り合いたいと言う軽い採用的な目的もそれなりにあった時期だったと思う。そのため、もくもくした後はケータリングの軽食とドリンクを提供した懇親会をやっていた。この時期の運営は別の人がやっており、私の役割は当日もくもく会のホストを務めるくらいだったと思う。ホストとして何をやっていたかというと、会の進行のみである。会の規模も定員10名程度とかなり小規模なものだ。最初に会場の案内をして、参加者全員に自己紹介と今日やることを一言ずつ言ってもらい数時間ひたすらもくもく。時間が来たら成果発表を簡単にしてもらって後は懇親会に移行する流れだ。
今見てみると不思議なことに第1回から参加者も豪華だ。(なぜみんな参加してくれたかは謎。)
ただ、この時期は直接的な採用の実績はなく、しかも運営側の負担が大きかった。(具体例をあげると、ケータリングの調整や平日開催だったため割と遅い時間まで拘束されるなど。)これはおそらく弊社がグルメサービスを運営している会社であり、ケータリングもかなり気合を入れる文化があったので特殊だったかもしれない。
何度か開催した後、コストがかかる・開催に関わる人の負担が大きいこと、後は現場のエンジニアが運営していないと言う理由でこの形態はやめた。特に後者は定期的に開催できない理由の大きな原因だったように思う。これはいい学びだったので、次からは主催を自分に移してもらうようにした。
2.「もくもくのみ」期
反省を生かし、この時期は「なるべく負担が少なく、かつ本当にやりたいことだけをやる」にフォーカスしていた。本当にやりたいこととは、勉強会コミュニティの運営とした。 ケータリングの発注はなし、作業しながら飲めるドリンクだけ用意していた。後は本当にもくもくするのみ。だってもくもく会だもの。ただし参加者の自己紹介だったり、何をしているかなどのコミュニケーションは取れるようにしていた。
この時期に学んだことは、「もくもく会で軽食の用意はなくても来る人はくるし、来ない人は来ない」である。あと、懇親会は苦手だというエンジニアはいると思うので、もくもく後の懇親会は逆に参加のハードルをあげている部分もあったのではと振り返って思う。
「もくもく + 質問コーナー」期
第9回くらいに転機が訪れた。
この日は台風16号の影響で天気が荒れており当日参加者は2人しか来なかったのだが、この2人がAndroidを始めたばかりという特殊な回だった。2人とも周りにAndroidのことを聞ける人がおらず、もくもく会というよりは質問会になったのだが、それがとても楽しかった。
Androidを始めるとだいたいみんなぶつかるであろう「FragmentとActivityのライフサイクル」など、"ちょっと経験のある誰かに聞いてみたいこと"が次々に飛び出して来て盛り上がった。懇親会とはちょっと違う空気感で、ただただ技術について話すのはとても楽しかったのだ。だってエンジニアだもの。
これが楽しかったので次回からもくもくした後に「せっかくの機会なので他社のエンジニアに聞いてみたいことコーナー」を作るようになった。意外とほぼ毎回のようにいろんな質問が飛び出して来るのでやってよかったなと思う。この形態にしてから参加者の方から「うちでも同じフォーマットでもくもく会始めました」と言ってもらうことが出て来て、参加者側から見ても満足してもらえていると思っている。
他社への影響
観測している範囲で3社ほど弊社のフォーマットでもくもく会を始めてくれている。一時期もくもくコンサルでお小遣い稼ぎしたいと思ったけど、一度参加すれば簡単に真似できるフォーマットなので諦めた :P
気をつけていること
もくもく会を運営していくにあたって、いくつか気をつけていることがある。
日程について
日程はとても大切で、ポイントとしては大きな勉強会とかぶる日程は避けるという点である。potatotipsやshibuya.apkとうっかり日程を被せてしまった時は本当に人が来なかった。ターゲット層が多数参加するような勉強会が開催される日は避けた方良い。
Twitter ハッシュタグについて
「もくもくのみ」期あたりから勉強会用にTwitterハッシュタグを用意した。静かにもくもくしている中で声をあげづらいけど聞きたいことやエアコン下げてほしいなどの要望を掬うことが当初の目的だったが、意外といい副作用があった。Twitter界隈で広くエンジニアと繋がっている人が勉強会の広告塔になってくれるのだ。これは他の勉強会でも同じだと思うが、「〇〇さんがいつも楽しそうにつぶやいているので一度参加してみたかった」と言われた時は嬉しかった。
ちょっと失敗したと思うのはAndroidらしくアッパーキャメルケース (#AndroidMokuMokuRetty) でハッシュタグを作ってしまったこと。一般的に広く使われるスネークケースにすればよかった。しかも上記であげている他社のもくもく会もみんなこれに倣ってアッパーキャメルケースにしてくれておりなんだか申し訳ない。
余談だが、メルカリのAndroidもくもく会のハッシュタグは#mokumoku_android でだいぶ攻めたなと思っている。さすが岡野さん…
そろそろ向かう。それにしても思い切ったハッシュタグだなぁ。(自分のとこは一応他社と被らないように社名を入れた) #mokumoku_android
— むーむー (@muumuumuumuu) 2017年1月31日
さいごに
これだけ思い入れのある勉強会だが、おそらく私が主催するのは多くて後2回ほどである。この記事を読んで興味を持った人がいたらぜひ参加してほしい。(募集人数は随時増やせるので枠は気にせずとりあえず参加ポチってください。)久しぶりの人もお別れ会だと思ってぜひ参加してほしい。
Custom View を作るときに気をつけること
Custom Viewの背景画像を変えたいときにちょっとやらかしたのでメモる٩( 'ω' )و
何が起こったか
Custom Viewを作って動的に背景画像を変えようとして切り替わらない(ように見える)現象に遭遇。
レイアウトファイルで指定していた背景をコードから動的に切り替えようとしたときに、なぜか切り替わらないように見える 😇
何がおかしいか皆さんも考えながらコードをご覧ください。
コード
レイアウトファイル (layout_custom.xml)
静的に赤丸のdrawableを背景に指定しています。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:background="@drawable/red_oval" android:layout_width="match_parent" android:layout_height="match_parent"> </LinearLayout>
CustomViewの本体コード (CustomView.kt)
初期化時に上記のレイアウトファイルを読み込んでいます。viewの生成が完了し、attachされたタイミングで動的に背景を黒丸に変更しています。
class CustomView : LinearLayout { constructor(context: Context?) : super(context) constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) init { LayoutInflater.from(this.context).inflate(R.layout.view_custom, this) } override fun onAttachedToWindow() { super.onAttachedToWindow() this.setBackgroundResource(R.drawable.black_oval) } }
現実
さて、上記コードを書いたときに私は背景は黒丸が出ることを期待していましたが、実際には赤丸が表示されています😩
何がダメだったのか
さて、ここまで読んでくれた皆さんは原因に気がついたでしょうか。
今回の原因はCustom Viewでlayout fileをinflateするときにrootを this
に指定しているため、this
= Custom Viewクラス (Linear Layoutを継承しているクラス)のchildとしてlayoutファイルからinflateしたviewたちがぶら下がる構成になっていました。そのため、コードから this
に対して黒丸に背景を変更しても、その上にいる赤丸を背景に指定しているview (layout fileの一番上の階層にいるview)の背景が重ねて描画されているため見た目的には何も変わらないと言うオチになっていました。
どうすればよかったか
ちゃんと正しいviewに対してアクセスして背景を変えてやりましょう。(本当はちゃんとviewにidつけたりすべきですがサンプルなので見逃してください😇)
override fun onAttachedToWindow() { super.onAttachedToWindow() this.getChildAt(0).setBackgroundResource(R.drawable.black_oval) }
これで無事黒丸背景になりました!٩( 'ω' )و
KotlinとReduxをAndroidアプリに導入した話をしました。
先日自社のエンジニアイベントで「KotlinとReduxをAndroidに導入したら」という話をしてきました。質疑応答や懇親会でいろんな質問をいただいたのでここに残しておきます。
イベント
登壇したのはこちらのイベント。アプリだけでなくバックエンドやインフラ、果ては機械学習まで幅広いテーマを扱うイベントでした。聞き手の皆さんの知識もばらつきがありそうなので、割と丁寧にいろんなことを説明したつもりです。しかしその分内容を詰め込みすぎて早口に…
当日の雰囲気はこちらで。
登壇内容
内容はこちらのスライドをご覧ください。ちなみに、最後の方の時間があれば話そうと思っていたFluxの話は時間切れでしませんでした。
スライドにも乗っていますが、今回初めて会社としてOSS Libraryを公開しました。こちらについてもいくつか質問をいただきました。
質疑応答や懇親会で話したこと
Reduxについて
Q: Stateの持ち方を途中で変更すると大変じゃないですか?
A: 今の所変更したいケースは出てきていないのでわかりません。変更してない理由としては、必要最低限の情報しかReduxにのせていないからです。別のアプローチとしては情報を綺麗に正規化して持たせるという方法もあります。今はどちらのアプローチが良いのか、AndroidとiOSでそれぞれ試している最中です。
—–
Q: API通信はReduxの図のどこでやるんですか?
A: スライドには入れてませんが、実際にはActionを生成するCreatorProducerというのがいます。ここで、APIの戻り値の情報を適切なActionに詰めます。CreatorProducerが生成したActionがStoreにdispatchされるようになっています。
—–
Q: SharedPreferenceなどローカルに情報を退避する方法を採用しないのですか?
A: 採用するのもいいのですが、退避場所を含めると2箇所で情報を持つことになるのでReduxの原則から外れるのと、情報によって「(退避する・しないといった)管理の方法」がバラバラになるのを避けたかったので今の所採用していません。もしも退避するのであればmiddlewareでやるのがいいと思います。
—–
Q: フロントエンドでReduxはやったことがあるけれど、アプリにReduxを取り入れる時のメリットがうまくイメージできなかったのですが、どういった点でよかったですか?
A: AndroidアプリはActivityやFragmentの双方向の情報のやり取りが非常に面倒臭いので、一箇所にデータをまとめて各自がそこを監視するというReduxはその点でよかったです。
—–
Q: FluxではなくReduxにした理由は?
A: Fluxにしたい(=Storeを複数持ちたい)と思うタイミングが定期的にやってきますが、今の所Reduxにしています。Fluxにしたいと思ってしまう時は、だいたいActivity/Fragment間で情報の共有がしたいときなので、しばらく悩んだあとサボらずにinterfaceを書いています。もしかしたらそのうちFluxを試すかもしれません。
--ここから懇親会では話せなかったけど、書いてて思いついたので追記-- メモリマネジメントの観点でFluxにする可能性も将来的にあるかもしれません。 一時的にかなり大きなサイズの情報を扱う必要が出てきた時に、 不要になったらそのStoreごと破棄してメモリを解放させる必要が出てきた場合などです。 今の所この点では困っていませんがサービスによってはあり得ると思います。 --ここまで懇親会では話せなかったけど、書いてて思いついたので追記--
—–
Q: Reduxを始める時に、何を参考にするのがいいですか?
A: この動画がオススメです!AndroidではなくJS前提ですが、Reduxのポイントは抑えられると思います。
Redux入門にオススメの動画ですー https://t.co/LcsdiZpD0p #Retty_tech_cafe
— むーむー (@muumuumuumuu) 2017年9月1日
Kotlinについて
Q: 今Kotlinの割合はどれくらいですか?
A: Kotlin:Java = 1:2 くらいです。
—–
Q: JavaとKotlinのコードが混在していて困ったことはありませんか?
A: 特にありません。強いて言うなら久しぶりにJavaのコードに手を入れなくてはいけない時に、「あ、Kotlinじゃないからこの書き方できない…」ちょっと残念な気持ちになるくらいです。
OSSについて
Q: 今回初めてOSS Libraryとして公開したと思いますが、どういった理由から公開に至ったのでしょうか?
A: 公開したLibraryはReSwiftというOSS Libraryをリスペクトして作ったものです。なので、OSSに帰したいという気持ちが作ったメンバーの中にありました。そういう経緯からOSS公開に至りました。
—–
Q: OSSで公開することに対して、社内でハードルはありましたか?
A: 作った人が公開したいという気持ちがあれば基本的にそれを尊重したいというカルチャーだったので、ハードルは特にありませんでした。しかし、当然議論はありました。公開することしないことに対するそれぞれのメリットやリスクを比較して、公開に至りました。
おまけ
長々と語っていますが、実はまだストアに公開されているアプリにはほんの一部しかReduxを取り入れられていません… 早く綺麗に作って公開したい!
内容盛りすぎてめっちゃ早口になった気がする😇 たくさんReduxについて話したけど、リリースはもうちょっと待っててね!(現状のアプリは画面間で全然情報共有できてない🙇♀️🙇♀️🙇♀️) #Retty_tech_cafe
— むーむー (@muumuumuumuu) 2017年9月1日
localeに繁體中文を設定してもvalues-zhが選択されない
今回もマニアックなトピックです。
タイトルの通り。ちょっと困ったので調べて見た。
何が起こったか
下記どちらかに当てはまる場合、
- 端末で繁體中文を言語設定で選択している
- アプリ独自のlocale設定で
zh_HK
(中国語-香港)を設定している
res
下の values-zh/strings.xml
が読まれず res/strings.xml
が選択された。
例えば、values/strings.xml
と、
<resources> <string name="test">Test</string> </resources>
values-zh/strings.xml
がある場合、
<resources> <string name="test">測試</string> </resources>
言語設定を繁體中文にしていても測試ではなくTestが表示される。同じ中国語(zh)ならvalues-zh
が優先されると思いきや、そんなことない。
ここでvalues-zh/strings.xml
をvalues-zh-rHK/strings.xml
にすれば測試になってまあ当面困ることはないが釈然としない。
ちなみに確認環境はNexus 6P (OS 7.1.2) 実機。
N以降でおきてるっぽい
そういえばNからLocale周りで変更入ったな?🤔 というのを思い出した。
試しにEmulator (Nexus 5 / API 23) で動かして見たらvalues-zh/strings.xml
が選択されて測試が表示された。
同じEmulator環境でもN系のものはだめ。(Nexus 6P / API 25とNexus 5X / API 23 で確認。)
一瞬Multi localeになった影響でEnglishがlocale listの二番目にきている影響か?と思ってEnglishを削除して見たけどだめ。
よく考えたらそもそもvalues-en
を作ってないテスト環境でも再現したから影響しようがなかった。
Frameworkのコードを読んでみよう
N以降のFrameworkのコードを読んでみる。ResourcesImpl.java#320 あたりから見ていくと読みやすいかな。
この辺りのコードでbestLocale
を取ってきて設定しているっぽい。
320 public void updateConfiguration(Configuration config, DisplayMetrics metrics, 321 CompatibilityInfo compat) { 351 // If even after the update there are no Locales set, grab the default locales. 352 LocaleList locales = mConfiguration.getLocales(); 353 if (locales.isEmpty()) { 354 locales = LocaleList.getDefault(); 355 mConfiguration.setLocales(locales); 356 } 357 358 if ((configChanges & ActivityInfo.CONFIG_LOCALE) != 0) { 359 if (locales.size() > 1) { 360 // The LocaleList has changed. We must query the AssetManager's available 361 // Locales and figure out the best matching Locale in the new LocaleList. 362 String[] availableLocales = mAssets.getNonSystemLocales(); 363 if (LocaleList.isPseudoLocalesOnly(availableLocales)) { 364 // No app defined locales, so grab the system locales. 365 availableLocales = mAssets.getLocales(); 366 if (LocaleList.isPseudoLocalesOnly(availableLocales)) { 367 availableLocales = null; 368 } 369 } 370 371 if (availableLocales != null) { 372 final Locale bestLocale = locales.getFirstMatchWithEnglishSupported( 373 availableLocales); 374 if (bestLocale != null && bestLocale != locales.get(0)) { 375 mConfiguration.setLocales(new LocaleList(bestLocale, locales)); 376 } 377 } 378 } 379 }
では「何がbestなのか」を評価するためのロジックはどうなっているかというと、LocaleList.java#302 あたり。
302 @IntRange(from=0, to=1) 303 private static int matchScore(Locale supported, Locale desired) { 304 if (supported.equals(desired)) { 305 return 1; // return early so we don't do unnecessary computation 306 } 307 if (!supported.getLanguage().equals(desired.getLanguage())) { 308 return 0; 309 } 310 if (isPseudoLocale(supported) || isPseudoLocale(desired)) { 311 // The locales are not the same, but the languages are the same, and one of the locales 312 // is a pseudo-locale. So this is not a match. 313 return 0; 314 } 315 final String supportedScr = getLikelyScript(supported); 316 if (supportedScr.isEmpty()) { 317 // If we can't guess a script, we don't know enough about the locales' language to find 318 // if the locales match. So we fall back to old behavior of matching, which considered 319 // locales with different regions different. 320 final String supportedRegion = supported.getCountry(); 321 return (supportedRegion.isEmpty() || 322 supportedRegion.equals(desired.getCountry())) 323 ? 1 : 0; 324 } 325 final String desiredScr = getLikelyScript(desired); 326 // There is no match if the two locales use different scripts. This will most imporantly 327 // take care of traditional vs simplified Chinese. 328 return supportedScr.equals(desiredScr) ? 1 : 0; 329 }
まず完全にLocaleが一致するか見ている。ここは一致しないはずなので次へ。
次にLonguageが一致しないか見ている。ここは一致するので次へ。
次にPseudo Localeか見ている。ここは当てはまらないので次へ。
最後ににscriptが一致するかを見ている。ISO15924で繁体字は Hant
と定められていて、これがいわゆる中国語の簡体字Hans
とは別扱いだった。
わざわざコメントでも書いてあった。
There is no match if the two locales use different scripts. This will most imporantly take care of traditional vs simplified Chinese.
まとめ
というわけで、values-zh
は簡体字扱いなので、端末の設定を繁體中文(繁体字)にしても文字が違うということで選択されなかったという結論になった。スッキリ!
LocaleといえばLanguageとCountry (region) くらいしか気にしていなかったけれど、script(文字) も気にしないといけなかった。 Referenceにも当然だけどscriptのことが書いてあった。ただ、これだけだとscriptがmatchingのロジックにどう関わるかわからないんじゃないかなぁ… というわけでたまにはFrameworkのコード読むの大事!٩( ‘ω’ )و