言いたいことはそれだけか

KotlinとかAndroidとかが好きです。調べたことをメモします。٩( 'ω' )و

[海外対応] AndroidのLocale周りまとめ [多言語対応]

はじめに

複数の国や地域、あるいは言語に対応する場合に避けられないMulti Locale対応ですが、Android Frameworkのバージョンによってできること・しなくてはならないことが変わって来ます。 各APIでの大きな変化を時系列でまとめてご紹介します。 また、実際にMulti Localeに対応して来た中で遭遇したハマり所とその対策方法・tipsなども共有していきます。

ってDroidKaigi2018のCfPに応募したけどRejectされたのでブログにまとめます😇 「急にグローバル対応しなきゃいけなくなったけど何していいかわかんない!」みたいな人にざっと見ていただきたいような内容です。

それでは各APIで何が対応されたのかまとめます。ハマり所とその対策方法とかは別のエントリで書けたらいいな。

Locale周りの変遷

API Level 1 : Localeの基礎

LocaleクラスはAPI Level 1の時代から存在している。というかAndroidで独自に定義したものではなく、java.util.Localeを使っている。そもそもLocaleってなに?という話をすると、ドキュメントには

A Locale object represents a specific geographical, political, or cultural region.

と定義されている。単に国というわけではなく、地理的・政治的・文化的な特定の地域をさすものである。例を挙げると、日本だとja-JPになる。これはlanguageとcountry(region)を合わせたものだ。この頃はlanguage codeについてはISO 639-1、region codeについてはISO 3166-1で定義されているものが使える。後述するが、API Level 24以降ではIETF BCP 47をサポートする。

「地理的・政治的・文化的」という部分について補足すると、languageにおいて同じ中国語でも簡体字繁体字が別のものとして扱われていたり、regionの方も中国(本土)と香港と台湾が別のコードが割り当てられている例がわかりやすいかもしれない。

さて、話をAndroidに戻そう。LocaleオブジェクトはContextからgetResources()getConfiguration()と経由して、Configurationのpublicなメンバであるlocaleにアクセスすることができる。アプリやActivity独自でLocaleの設定を持ちたい場合は、ApplicationやActivityのContextのlocaleを書き換えることで言語切り替えを行うことができる。具体的にはいい感じにLocaleを書き換えたConfigurationオブジェクトを使って Resources#updateConfiguration()を呼んでやるとできる。(端末の言語設定に従う場合は端末で設定したLocaleがここに入ってくるので特に個別のアプリで対応する必要はない。)端末の言語設定で何が選択されているか知りたい時にはLocale#getDefault()が使える。

では各Localeごとのリソースをどう定義するか。ドキュメントはこの辺りを参照してほしいが、ざっくりいうとres/配下のディレクトリにconfig_qualifierを付与することでLocaleごとのリソースを定義することができる。ディレクトリ名のルールはこのようにハイフンつなぎ。

<resources_name>-<config_qualifier>

例を挙げると、 アメリカ・英語用の言語リソースは下記のように定義できる。valuesresources_nameenrUSconfig_qualifierだ。

values-en-rUS/strings.xml

上記の例のように、qualifierはハイフンで複数繋げることができる。Localeクラス同様languageは ISO 639-1、regionは ISO 3166-1で定義されたものが使える。ただし、regionの方は頭にrをつけなければならない。なぜかというとcase sensitiveではないので、ハイフンでqualifierを繋げた場合にregionを明示的に示すために必要なのだ。また、regionを指定する場合は必ずlanguageとセットで指定しなければならない。(region単体では使えない。)

例ではvalues/を示したが、values以外にももちろん使える。例えばdrawable/でも文字埋め込み画像の出し分けをするのに必要だし、xml/menu/でLocaleによって機能の出し分けをするのに使うことができる。

API Level 17 : RTL対応

ここからConfigurationクラスにsetLocale()メソッドが追加された。とは言え、このクラスのメンバであるlocaleはpublicのままだ。では直接localeの値を書き換えるのとsetLocale()メソッドでやっていることは何が違うかコード見てみよう。

     /**
      * Set the locale. This is the preferred way for setting up the locale (instead of using the
      * direct accessor). This will also set the userLocale and layout direction according to
      * the locale.
      *
      * @param loc The locale. Can be null.
      */
     public void setLocale(Locale loc) {
         locale = loc;
         userSetLocale = true;
         setLayoutDirection(locale);
     }

まずはlocaleの更新を行なっている。これは予想通りだろう。次にuserSetLocaleフラグをtrueにしている。このフラグはpublicだがhideアノテーションが付いている。これはActivityManagerServiceがsystem propertyにlocale情報を書き込む時に使われる。 そして最後のsetLayoutDirection()メソッド。これもAPI Level 17から追加されたAPIだ。何をしているかと言うと、どちらからどちらの方向にレイアウトするをLocaleから判断している。

「どちらからどちらにレイアウトするか」と聞いてピンと来ない人がいるかもしれない。我々が普段目にする文字は(横書きの)日本語であれば左から右方向に流れる。よく目にする英語もそうだ。しかしアラビア語ヘブライ語など一部の言語では横書きでも右から左に文字を記述する。右から左、なのでRight to Left, 頭文字をとってRTL言語と言われる。文字だけでなく、ボタンの位置などもRTL方向に配置する必要があるが、言語によってレイアウトを複数分けて用意する必要はない。例えばRelative Layoutの子要素として、TextViewとその右にButtonを置きたいと思ったらandroid:layout_toRightOf ではなくandroid:layout_toEndOfを使おう。これもAPI Level 17から追加されたattributeだ。このattributeを使っているとsetLocale()を使ってLocaleを更新した際に画面の再描画が走って適切なLayout Directionで表示されるようになる。

API Level 24 : Multi Locale

このAPIバージョンから java.util.Locale が変わってIETF BCP 47をサポートするようになった。これによりサポートされる言語が大幅に増えた。 また、Multi LocaleをサポートするようになったのもAPI Level 24からだ。

Multi Localeについては以前ブログにまとめた。

muumuutech.hatenablog.com

詳細は上記を読んでもらうとして、ざっくり概要を書くと「これまではユーザは言語を一つしか設定できなかったが、このバージョンからは複数の言語とその優先順位を設定でき、かつシステム側もいくつかLocaleを準備しておくことによってより良いマッチングを行うことができる」ものである。

いくつかの言語をユーザが設定できるという部分だが、これに伴いConfigurationクラスのAPIにいくつか変更が入った。具体的には単一のLocaleを引数に取る setLocale() 、またpublicメンバのlocale がdeperecatedになった。代わりに使用が推奨されるのがLocaleListクラスを引数に取るsetLocales()だ。

    public void setLocales(@Nullable LocaleList locales) {
         mLocaleList = locales == null ? LocaleList.getEmptyLocaleList() : locales;
         locale = mLocaleList.get(0);
         setLayoutDirection(locale);
     }

3行のシンプルなメソッドだ。Localeのリストを更新して、その優先順位トップのものを現在のLocaleとして設定、最後に現在のLocaleに応じたLayout Directionを設定する。

ちなみにLocaleListクラスがマッチングをやってくれる。ロジックはこの辺。以前blogにもちょっと書いた。

muumuutech.hatenablog.com

API Level 25 : Configurationの設定方法が変わった

Resources#updateConfiguration() がdeprecatedになった。代わりに何を使うのが推奨されているかというと、Context#createConfigurationContext()だ。(このAPIが追加されたのはAPI Level 17。)ただし、これは引数で指定したConfigurationが設定された新しいContextオブジェクトを返してくれるだけだ。ここで生成したContextをActivity#attachBaseContext()に渡してやると新しいConfiguration(と、これが持っているLocale)が設定される。

Twitterで教えてもらいました。感謝!)

API Level 26: Configurationの共有範囲が変わった

API Level 25でdeprecatedになったResources#updateConfiguration()だが、25までは一度コールするとその設定がapplication内で共有されていたが、API Level 26からは各activity/applicationで別の設定になった。

このあたりの記事が参考になった。

proandroiddev.com

最後に

以上でざっとLocale周りの変遷を書いた。最後の方とかLocaleともはや直接関係ないんじゃ…という気がしなくもないが、多言語、海外対応する際に必要になるケースも多いのではないかと思うので消さずに残しておく。

それにしてもAPI Level が上がれば上がるほどアプリ内で独自の言語設定させていかないぞ♡というGoogle先生のお気持ちが強まっているのかなと思わずにはいられない😇 😇 😇

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がいくつか追加された。AndroidAPI 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-apilint-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を作ることはできた。f:id:muumuumuumuu:20180106162609p:plain

感想

Custom ListをよりによってKotlinで作ろうとして一番大変だったのは、とにかくドキュメントがないこと。公式もそうだが、全体的に情報が少ない。ヒットしても数年前のものだったりして現状で動かなくなったりして困った。無駄にCustom Listの中のコードを読んでちょっと詳しくなってしまった気がする… もしもどこかに体系化された素晴らしいドキュメントがあれば教えてください(´;ω;`)

Links

実装にあたりこれらの記事を参考にした。


qiita.com


tools.android.com


Help developers with custom Lint rules · Jeremie Martinez


www.slideshare.net


www.bignerdranch.com


qiita.com


  1. Jet Brains 公式ドキュメントではkotlin-stdlib-jre7kotlin-stdlib-jre8を使えと書いている。kotlin-stdlib-jre7だけを指定するとJava 8 に依存した機能を使った時にコンパイルエラー出してくれるかな?と期待したが、残念ながらエラーは出なかった。kotlin-stdlib-jre8を指定しようと思ったらAndroidではJack and Jillへ依存を持っているようでgradleのsyncすらできなかった。(Jack and Jillはdeprecatedになりましたね…)

  2. むしろデフォルトでチェックがついているJava 8 migration aidsの Replace with single Map methodでMap.getOrDefault()を使うように推奨してきます😇

2017年を振り返る

はじめに

2017年に自分が何をしていたか後から振り返られるようにメモを残しておく。 shirajiさんのエゴサで振り返るというアイディアがとても素敵だったので彼に倣ってtweetをペタペタしていこう。

shiraji.hatenablog.com

1月

自分が業務でやっているサービスのAndroid Appが初の海外対応をリリースして浮かれていた。この時に割と多言語・他地域対応の知見が溜まったのでよかった。

ここでちょっと業務が落ち着いたので以降はDroidKaigiの準備に追われていたように思う。

2月

今まで怖くて入れなかったandroid-jpのslack channelに入ってみた。最近は #generalより #english の方が活発な不思議なコミュニティだった。

そうか、けもふれは今年の2月か…

この頃からランニングを再開していたようだ

3月

DroidKaigiにスピーカーとして参加した。最高に楽しかったけど、今年はrejectされたので聞く側で楽しんできます :p この頃はずっとねこ、えさ、ねこ、って言ってたきがする

muumuutech.hatenablog.com

決める――すべてを一瞬で判断できるシンプルな技法

決める――すべてを一瞬で判断できるシンプルな技法

この時期に読んだこの本はよかった。タスクを分類して意識的にどちらかを減らしてどちらかを増やすということはこれからも意識していきたい。

4月

この頃からPodcastを大量に聴き始める。そういう意味で今年はインプットの方法が大きく変わった。通勤中とかもうほとんど音楽聞かずにPodcastばかり聴いている。

Androidを支える技術〈I〉──60fpsを達成するモダンなGUIシステム (WEB+DB PRESS plus)

Androidを支える技術〈I〉──60fpsを達成するモダンなGUIシステム (WEB+DB PRESS plus)

Androidを支える技術、今年はAndroidエンジニアの中で流行ったなー。いい本だ。

5月

KotlinがAndroid公式サポートされた。便乗して会社のTech Blogを書いていた。

自宅の引越しを行ったのもこの時期。都民デビュー。

6月

今度は自宅ではなくオフィスも引越した。

引越し直前にオフィス近くの人気店とだかを貸し切って美味しい食事をしていた。

SHIROBAKOに手を出し始めたのもこの頃。

7月

Swiftに手を出し始めたようだ。

アラサーにして初めてまともにドラクエをみた。夫がPS4ドラクエ11をやってるのをところどころチラ見していたけど衝撃のストーリー展開にびっくりしすぎて自分でもやり始めた。セーニャちゃんと結婚したかった…

8月

Lisp本を読み切った。なんだかんだネタ枠と言いつつ意外とよかった…

会社でやっているもくもく会が20回を突破。 もくもく会については最近ポエムも書いた。

muumuutech.hatenablog.com

Swift入門枠で登壇もやった。

Fearless Change アジャイルに効く アイデアを組織に広めるための48のパターン

Fearless Change アジャイルに効く アイデアを組織に広めるための48のパターン

omoiyari.fm でおなじみのFearless change、すごくいいことが書いてた。でもやっぱり実践するのは難しいなーと思い知った年でもあった。

9月

muumuutech.hatenablog.com

KotlinとReduxの話を自社イベントでしてきた。

Android エンジニアTシャツ、この日以降まだ出番はない。

10月

この頃からTwitterをはじめ色々なwebサービスで設定しているアイコンを自分の顔写真にした。

11月

DroidKaigi 2018のCfPが不採択でショックを受けるなか、その日は外部の勉強会でKotlinとサービスについて話してきた。

ハーフマラソンを走った。20km以上走るのは7年前のフルマラソン以来だったので不安だったが無事完走。

ずっとファンだったPodcastにゲストとして呼んでもらった。感動した。

12月

社内のエンジニア向けに「Androidではじめの方に知っておきたかったこと講習会」みたいなことをやり始めた。これは機会があれば資料を綺麗に作り直してどこかに出したい。

仲間内でクローズドなPodcastごっこを始めた。

来年

DroidKaigi 2018のセッションの解説をする。プレミアムフライデーって本当に実在するのか…。解説陣がgfxさんshirajiさんと(私はともかく)豪華なので楽しいと思うのでぜひご参加ください。 connpass.com

おまけ

夫が可愛いシリーズ

KotlinJSでReact Nativeを書いてみたかった話

はじめに

この記事は「KotlinJSでReact Nativeを書いてみたかった話」です。つまりできなかった話 であることをあらかじめご了承ください。


では本題。

年末になり会社のAdvent Calendarのネタ探しをしている最中に電波が降りて来た。

当時Kotlinが好きすぎてちょっとおかしくなっていたのか、「SwiftではなくKotlinでiOSかけたら最高では?😇 」「でもKotlin/Native で書くのはちょっとまだ早すぎるよな〜〜😓」「あれ?KotlinJSでReactNativeいける!?!?」

ネタとしてもちょっとおもしろいかなと思いやってみることに。

ちなみにKotlin/Nativeまだ実務ではつらいよねって話は同僚がまとめてくれた記事が良いので興味ある人はぜひ読んでほしい。せめて補完が効くようになったら起こしてください( ˘ω˘ ) スヤァ…

qiita.com

React NativeとKotlinについて

React NativeはJavascriptで記述する。その名の通りReactベースなのだが、コンポーネント部分をそれぞれのプラットフォームのブリッジでごにょごにょしてくれるのでnativeとして動く。つまり、AndroidであればJava1iOSであればSwiftのコードになる。2

一方Kotlinは1.1よりJavascriptへのコンパイルをサポートした。どういうことかというと、Kotlinで書いたコードがJavascriptにconvertされるということ。

これらを組み合わせて、

  1. React NativeでJavascript部分をまずはKotlinで書いて、
  2. それをJavascriptに変換し、
  3. さらにブリッジを経由してJavascriptが対応するSwiftもしくはJavaのコードとして動作

これにより実質KotlinでReactNativeでは?というのがやりたいことになる。

やってみた

まずはReact Nativeのプロジェクトを用意する

このへんをみて適当にプロジェクトを作る。まずはデフォルトで生成されるJavascriptがちゃんとアプリで表示されることを確認。デフォルトの画面が表示されえたのでOK。

JSのコードをKotlinに置き換えたかった

Kotlin JS がサポートしているビルド方法は下記4種類。

それぞれCompile後にjsファイルが吐き出される。

React NativeはReactで動いているので、まずはReactをimportする。 Kotlinを開発しているJetBrains社がWrapperを用意してくれているのでこいつをinstallする。

npm i @jetbrains/kotlin-react

次にReactNativeのコンポーネント部分をimportしよう、と思ったところでそんなものは世の中に存在しないことに気がついてしまった。

自分で書くにはJS力足りないし、何よりもはやAdvent Calendarのネタとしては重すぎるよな〜〜というわけでお蔵入りになりました :p


というわけでボツネタ供養でした :pray:



  1. 独自コンポーネントをKotlinでかけばKotlinに変換されるという理解

  2. React Nativeは軽く技術検証したくらいしか触ってない。業務でがっつり使ったわけではないので間違ったこと書いてたらすみません…

小規模もくもく会運営についての知見とポエム

この記事は勉強会運営 Advent Calendar 2017の19日目の記事です

adventar.org

はじめに

会社でもくもく会を運営しているが今年はついに20回を超え、ありがたいことに他社からも「御社のもくもく会パクリ 参考にしてうちでももくもく会はじめました 」と言ってもらえることが増えた。勉強会運営としてはある程度いい感じに回っている気がするので誰かの参考になればと知見とポエムを書き残しておきたいと思う。

注意事項

上記で「いい感じに回っている」と記載したが、それは健全な勉強会コミュニティを形成すると言う意味であり、採用目的(「いいエンジニアをシュッと採用したい」など)の観点ですぐに効果が出ることを期待しているのであれば、おそらくこの記事はあなたの求めているものではない。

Androidもくもく会@Rettyオフィス」の歴史

私が運営しているのは「Androidもくもく会@Rettyオフィス」というAndroidエンジニアを対象としたもくもく会である。この勉強会はもうすぐ2年ほど経つが、2年間の間に色々模索しながら形態を変えてきた。 形態によって大きく分けて3つの時期に分けられ、それぞれどのような学びがあったかをまとめたいと思う。

1. 「もくもく + 懇親会」期

そもそもこの勉強会自体は私がはじめたものではない。(第1回目は参加すらしていない。)もともと他社のエンジニアに弊社のことを知ってもらったり、業界のエンジニアと知り合いたいと言う軽い採用的な目的もそれなりにあった時期だったと思う。そのため、もくもくした後はケータリングの軽食とドリンクを提供した懇親会をやっていた。この時期の運営は別の人がやっており、私の役割は当日もくもく会のホストを務めるくらいだったと思う。ホストとして何をやっていたかというと、会の進行のみである。会の規模も定員10名程度とかなり小規模なものだ。最初に会場の案内をして、参加者全員に自己紹介と今日やることを一言ずつ言ってもらい数時間ひたすらもくもく。時間が来たら成果発表を簡単にしてもらって後は懇親会に移行する流れだ。

connpass.com

今見てみると不思議なことに第1回から参加者も豪華だ。(なぜみんな参加してくれたかは謎。)

ただ、この時期は直接的な採用の実績はなく、しかも運営側の負担が大きかった。(具体例をあげると、ケータリングの調整や平日開催だったため割と遅い時間まで拘束されるなど。)これはおそらく弊社がグルメサービスを運営している会社であり、ケータリングもかなり気合を入れる文化があったので特殊だったかもしれない。

何度か開催した後、コストがかかる・開催に関わる人の負担が大きいこと、後は現場のエンジニアが運営していないと言う理由でこの形態はやめた。特に後者は定期的に開催できない理由の大きな原因だったように思う。これはいい学びだったので、次からは主催を自分に移してもらうようにした。

2.「もくもくのみ」期

反省を生かし、この時期は「なるべく負担が少なく、かつ本当にやりたいことだけをやる」にフォーカスしていた。本当にやりたいこととは、勉強会コミュニティの運営とした。 ケータリングの発注はなし、作業しながら飲めるドリンクだけ用意していた。後は本当にもくもくするのみ。だってもくもく会だもの。ただし参加者の自己紹介だったり、何をしているかなどのコミュニケーションは取れるようにしていた。

この時期に学んだことは、「もくもく会で軽食の用意はなくても来る人はくるし、来ない人は来ない」である。あと、懇親会は苦手だというエンジニアはいると思うので、もくもく後の懇親会は逆に参加のハードルをあげている部分もあったのではと振り返って思う。

「もくもく + 質問コーナー」期

第9回くらいに転機が訪れた。

retty.connpass.com

この日は台風16号の影響で天気が荒れており当日参加者は2人しか来なかったのだが、この2人がAndroidを始めたばかりという特殊な回だった。2人とも周りにAndroidのことを聞ける人がおらず、もくもく会というよりは質問会になったのだが、それがとても楽しかった。

Androidを始めるとだいたいみんなぶつかるであろう「FragmentとActivityのライフサイクル」など、"ちょっと経験のある誰かに聞いてみたいこと"が次々に飛び出して来て盛り上がった。懇親会とはちょっと違う空気感で、ただただ技術について話すのはとても楽しかったのだ。だってエンジニアだもの。

これが楽しかったので次回からもくもくした後に「せっかくの機会なので他社のエンジニアに聞いてみたいことコーナー」を作るようになった。意外とほぼ毎回のようにいろんな質問が飛び出して来るのでやってよかったなと思う。この形態にしてから参加者の方から「うちでも同じフォーマットでもくもく会始めました」と言ってもらうことが出て来て、参加者側から見ても満足してもらえていると思っている。

他社への影響

観測している範囲で3社ほど弊社のフォーマットでもくもく会を始めてくれている。一時期もくもくコンサルでお小遣い稼ぎしたいと思ったけど、一度参加すれば簡単に真似できるフォーマットなので諦めた :P

robust-inc.connpass.com

oi-study.connpass.com

eure.connpass.com

気をつけていること

もくもく会を運営していくにあたって、いくつか気をつけていることがある。

日程について

日程はとても大切で、ポイントとしては大きな勉強会とかぶる日程は避けるという点である。potatotipsやshibuya.apkとうっかり日程を被せてしまった時は本当に人が来なかった。ターゲット層が多数参加するような勉強会が開催される日は避けた方良い。

Twitter ハッシュタグについて

「もくもくのみ」期あたりから勉強会用にTwitterハッシュタグを用意した。静かにもくもくしている中で声をあげづらいけど聞きたいことやエアコン下げてほしいなどの要望を掬うことが当初の目的だったが、意外といい副作用があった。Twitter界隈で広くエンジニアと繋がっている人が勉強会の広告塔になってくれるのだ。これは他の勉強会でも同じだと思うが、「〇〇さんがいつも楽しそうにつぶやいているので一度参加してみたかった」と言われた時は嬉しかった。

ちょっと失敗したと思うのはAndroidらしくアッパーキャメルケース (#AndroidMokuMokuRetty) でハッシュタグを作ってしまったこと。一般的に広く使われるスネークケースにすればよかった。しかも上記であげている他社のもくもく会もみんなこれに倣ってアッパーキャメルケースにしてくれておりなんだか申し訳ない。

余談だが、メルカリのAndroidもくもく会ハッシュタグは#mokumoku_android でだいぶ攻めたなと思っている。さすが岡野さん…

さいごに

これだけ思い入れのある勉強会だが、おそらく私が主催するのは多くて後2回ほどである。この記事を読んで興味を持った人がいたらぜひ参加してほしい。(募集人数は随時増やせるので枠は気にせずとりあえず参加ポチってください。)久しぶりの人もお別れ会だと思ってぜひ参加してほしい。

retty.connpass.com

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

現実

さて、上記コードを書いたときに私は背景は黒丸が出ることを期待していましたが、実際には赤丸が表示されています😩

f:id:muumuumuumuu:20171029162115p:plain:w200

何がダメだったのか

さて、ここまで読んでくれた皆さんは原因に気がついたでしょうか。
今回の原因は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)
}

これで無事黒丸背景になりました!٩( 'ω' )و

f:id:muumuumuumuu:20171029163326p:plain:w200

KotlinとReduxをAndroidアプリに導入した話をしました。

先日自社のエンジニアイベントで「KotlinとReduxをAndroidに導入したら」という話をしてきました。質疑応答や懇親会でいろんな質問をいただいたのでここに残しておきます。

イベント

登壇したのはこちらのイベント。アプリだけでなくバックエンドやインフラ、果ては機械学習まで幅広いテーマを扱うイベントでした。聞き手の皆さんの知識もばらつきがありそうなので、割と丁寧にいろんなことを説明したつもりです。しかしその分内容を詰め込みすぎて早口に…

retty.connpass.com

当日の雰囲気はこちらで。

togetter.com

登壇内容

内容はこちらのスライドをご覧ください。ちなみに、最後の方の時間があれば話そうと思っていたFluxの話は時間切れでしませんでした。

speakerdeck.com

スライドにも乗っていますが、今回初めて会社としてOSS Libraryを公開しました。こちらについてもいくつか質問をいただきました。

github.com

質疑応答や懇親会で話したこと

Reduxについて

Q: Stateの持ち方を途中で変更すると大変じゃないですか?

A: 今の所変更したいケースは出てきていないのでわかりません。変更してない理由としては、必要最低限の情報しかReduxにのせていないからです。別のアプローチとしては情報を綺麗に正規化して持たせるという方法もあります。今はどちらのアプローチが良いのか、AndroidiOSでそれぞれ試している最中です。

—–  

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のポイントは抑えられると思います。

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を取り入れられていません… 早く綺麗に作って公開したい!