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

12kg

12kgは1歳男児、2歳女児の平均と同じくらいだそうです。

この3ヶ月ちょっとで12kg落とすことに成功したので、つまり僕は1歳男児 or 2歳女児を産んだということになります。 すごい。

大学入ってからの体重推移が激しすぎて、いつか身体を壊すのでは・・・?

年月 身長 体重 備考
2010/4 184cm 93kg デブ
2013/12 187cm 115kg クソデブ
2014/4 187cm 86kg デブ
2015/3 187cm 89kg デブ
2016/3 188cm 95kg クソデブ
2016/9 188cm 102kg クソデブ
2016/12 188cm 89kg デブ

※「階段を10段あがって息が切れないのがデブ、息が切れるのがクソデブ」です

新卒から見た、宮森あおいに見る責任感とマネジメント

この記事は SHIROBAKO Advent Calendar 2016 4日目です。 ハードルがどんどん上がってきているので、一回休み的な形で見ていただけるとありがたいです。

僕は武蔵野アニメーションの制作進行を務めるおいちゃんこと宮森あおいについて書きたいと思います。

f:id:jmatsu:20161203155915p:plain

※ 画像出展 キャラクター|TVアニメ「SHIROBAKO」公式サイト

宮森は新卒で武蔵野アニメーションに入り、1話時点で新卒1年目半年ほど、作中で2年目となり制作デスクを担当、そのまま最終話を迎えます。
経歴としては浅いですが、非常に優秀な制作進行・デスクとしてステークホルダーとの調整・仕事の割り振り・進捗管理等々の仕事をこなしています。

さて 1->2年目という流れの中で、その優秀さを見せつけてくれる宮森の「責任感」と類まれな「マネジメント能力」について、印象的なセリフを抜粋して書きたいと思います。

その前に・・・

序盤に見せる精神的な弱さ

宮森は他のキャラよりも多くの精神的な弱さ、精神的な軸のなさも表現されています。
特に序盤は成長前というところで軽く振り返ってみましょう。

「ドーナツ食べたいっ! ドーナツドーナツ!ドーンといきたいよぉ!」

f:id:jmatsu:20161204131523j:plain

1話。かわいい。わかる。

「私は、まだ具体的に何になりたいとか何がしたいとか目標がなくて。私には、何ができるのかなぁ。」

f:id:jmatsu:20161204130816j:plain

4話。
自分が仕事をする、していく上での悩みがこの自問自答にも見えるセリフに表れていますね。
しれっと会話相手である絵麻に褒めてもらいたい感じがかわいいです。

「なんだろう・・・私の最終目標って・・・」

f:id:jmatsu:20161204131021j:plain

7話。
ひとの最終目標なんて知らないけどかわいいですね。このシーンの前の絵麻の冷たい表情もいいです。

「みんな、夢とかあるんだな。」

f:id:jmatsu:20161204131322j:plain

8話。
そうなのかもわからないですが、かわいいなぁ。ハンドルになりたい。

圧倒的成長を遂げる責任感

さて、ここからが本題です。デスクになる直前辺りから一気に精神的に成長します。
まあ最初から仕事っぷりはおかしいと思うのですが、そこらへんは割愛して。

「今回は、最初に監督の考えを聞いておきたいんです。何がやりたいのか、どう作りたいのか。」

f:id:jmatsu:20161203173614j:plain

13話。2年目直前にデスクに任命され、そしてチームマネジメントとして最初にとった行動です。

アニメの制作には全く詳しくありませんが、プロダクトを作るという立ち位置で見ると、舵取りをする人の言葉や意識はそのままプロダクトの方向性に反映されます。
舵取り者(ここでは監督)が目指すプロダクトの方向性を把握することこそが、そのプロダクト制作において、そしてその進行を調整する立場として重要であることをありのまま伝えているわけです。
そして「今回は、」という言葉から制作進行のときの反省を活かし、より良いものを作ろうという意志が見て取れます。素晴らしい責任感です。

「ちゃんとコミュニケーション取って欲しいです。瀬川さんたちと。」

f:id:jmatsu:20161203174216j:plain

21話。平岡と瀬川さんの間に確執ができ、その仲裁をしたときのシーンです。

平岡の知り合いである磯川から「クリエイターと制作が共有出来る目標を一緒に粘り強く探し続けて落とし所を見つける事が大事なんだと思うね。こっちもクオリティーを大切にしてるかどうかって、ちょっとした事でクリエイターも感じ取れるんだよ。いい作品を作ろうと戦っているのはみんな同じなんだから」という言葉で宮森は吹っ切れます。

第三者から見てもコミュニケーションが取れていないという点、特に瀬川さんと取って欲しいという点、そして暗に瀬川さんたち以外ともコミュニケーションが取れていないという事実をつきつけながら、先に「コミュニケーションを取って欲しい」という想いを伝えるあたり、話術の才能を感じさせますね。
他者への敬意を持ち、正直に気持ちを伝え、そしてプロダクトを完成させるという「全員に共通するゴール」へ一直線で向かっている点が垣間見えます。
同じゴールを持ちながら、チームメンバーで足を引っ張りあい、争うことは非常に無駄な行為ですからやらないように気をつけたいです。

一緒に仕事をする人に対する敬意と信頼

この面に関しては、こんな人にマネジメントされたい・・・という思うくらい良いセリフが多々あります。
その中でも言われたいセリフ、そしてその考え方を真似したいと思える2つを抜粋しました。

「この仕事は杉江さんにしか出来ないと思います。」

f:id:jmatsu:20161204130149j:plain

12話。このセリフに出てくる杉江さんはベテラン作画であり、その腕は作中でも高い評価を受けていることが分かります。 そうは言っても「あなたにしか出来ない仕事をお願いしたい」「あなただからこそお願いしたい」という表現を腕が確かな人に、それも普段一緒の仕事場にいる人に伝えることは誰にでもできることでしょうか? またその飾り気のないセリフに宮森の誠実さがよく現れていると思います。
しかもこれが1年目というのはもうマネジメント向きの人間性を備え付け過ぎです。

こんな言葉を貰ってみたいものですね。宮森に「この仕事はデブにしか出来ないと思います。」と言われたらめっちゃ頑張ると思います。

「でも絶対戻せないってことじゃないですから!」

f:id:jmatsu:20161204132840j:plain

22話です。
瀬川さんからの信頼を一度失ってしまった宮森。矢野先輩の言葉を受けてのセリフなのですが、見方によっては、信頼を失うことについて宮森が甘く見ているのではないかと先輩から釘を差されたとも言えるでしょう。
このセリフですが、その精神力もさることながら、瀬川さんの指摘が「感情」ではなく「仕事とその責任感」から来たものであることを理解していないとこの発言は出ません。
まあ太郎が喋っていたら「図々しい」と評されるのかもしれませんし、僕は思いますが。

余談ですが、このセリフでググるとヨリを戻したい人達の投稿がいっぱいでてくるので検索しない方がいいと思います。

先輩としての一面

2年目に入り後輩が入ってきたときの宮森。いい先輩になっています。
来年はこんなメンターになりたいですね、といいたいんですが当分機会がなさそうなので心の内に秘めておきます。

「新人の制作も入ったんです。裏道覚えたらどんどん間違った方法に頼るように・・・」

f:id:jmatsu:20161203172725j:plain

16話。「融通きかねえな新人は。原画進めておいて、作監作業で直せばいいだろ。これもぷるんぷるんするぞ」という平岡のセリフに対する返しです。

仕事・プロダクトに対する責任感や先輩としての責任感を見ることができますね。
宮森は目の前の仕事だけではなくその先を見ていて、間違った方法に頼ったとき将来的に評価等含めて困るのはまずその人、そしてその悪影響は波及し、この場合は一緒に働く人(特に作監)へと広がるところまできっちりと視野に入れています。
平岡から見ると柔軟性がないと捉えられている通り、個々人のボーダーが違うという点は当然です。だからこそ、その前提の上でしっかりと意見を主張できる点が素晴らしいですね。

「そのうっかりをちゃんと覚えておいてね。覚えておけば、次の失敗はないから。」

f:id:jmatsu:20161203173340j:plain

17話。新人が入ってきて、初歩的なミスをして落ち込んでいるときにしたアドバイスです。

ただの優しさのようにも見えますが、自分に対する言葉のようにも見えますし、またそこから今までの業務姿勢を見ることができます。
自分自身がその姿勢でいなければ、こんな一言は伝えられないでしょう。

まとめ

かわいい。一緒に仕事したい。餌付けしたい。

f:id:jmatsu:20161203172221j:plain

おまけ

制作デスクのお仕事についてはこちら。 制作デスクの仕事 | P.A.WORKS Blog

3rd party libraryとしてやってはいけないログ出力

問題 : 何も成功していないのに成功したといってしまう

I/art: Rejecting re-init on previously-failed class java.lang.Class<com.google.android.gms.common.api.Status>
E/FirebaseApp: Firebase API initialization failure.
         java.lang.reflect.InvocationTargetException
             at java.lang.reflect.Method.invoke(Native Method)
   at com.google.firebase.FirebaseApp.zza(Unknown Source)
   at com.google.firebase.FirebaseApp.initializeApp(Unknown Source)
   at com.google.firebase.FirebaseApp.initializeApp(Unknown Source)
   at com.google.firebase.FirebaseApp.zzek(Unknown Source)
   at com.google.firebase.provider.FirebaseInitProvider.onCreate(Unknown Source)
   at android.content.ContentProvider.attachInfo(ContentProvider.java:1748)
   at android.content.ContentProvider.attachInfo(ContentProvider.java:1723)
   at com.google.firebase.provider.FirebaseInitProvider.attachInfo(Unknown Source)
   at android.app.ActivityThread.installProvider(ActivityThread.java:5153)
   at android.app.ActivityThread.installContentProviders(ActivityThread.java:4748)
   at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4688)
   at android.app.ActivityThread.-wrap1(ActivityThread.java)
   at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1405)
   at android.os.Handler.dispatchMessage(Handler.java:102)
   at android.os.Looper.loop(Looper.java:148)
   at android.app.ActivityThread.main(ActivityThread.java:5417)
   at java.lang.reflect.Method.invoke(Native Method)
   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
Caused by: java.lang.NoClassDefFoundError: com.google.android.gms.common.api.Status
   at com.google.android.gms.internal.zzqf.<init>(Unknown Source)
   at com.google.android.gms.internal.zzqf.zzcb(Unknown Source)
   at com.google.android.gms.measurement.internal.zzn.zzwv(Unknown Source)
   at com.google.android.gms.measurement.internal.zzaa.initialize(Unknown Source)
   at com.google.android.gms.measurement.internal.zzx.<init>(Unknown Source)
   at com.google.android.gms.measurement.internal.zzab.zzbum(Unknown Source)
   at com.google.android.gms.measurement.internal.zzx.zzdo(Unknown Source)
   at com.google.android.gms.measurement.AppMeasurement.getInstance(Unknown Source)
   at java.lang.reflect.Method.invoke(Native Method) 
   at com.google.firebase.FirebaseApp.zza(Unknown Source) 
   at com.google.firebase.FirebaseApp.initializeApp(Unknown Source) 
   at com.google.firebase.FirebaseApp.initializeApp(Unknown Source) 
   at com.google.firebase.FirebaseApp.zzek(Unknown Source) 
   at com.google.firebase.provider.FirebaseInitProvider.onCreate(Unknown Source) 
   at android.content.ContentProvider.attachInfo(ContentProvider.java:1748) 
   at android.content.ContentProvider.attachInfo(ContentProvider.java:1723) 
   at com.google.firebase.provider.FirebaseInitProvider.attachInfo(Unknown Source) 
   at android.app.ActivityThread.installProvider(ActivityThread.java:5153) 
   at android.app.ActivityThread.installContentProviders(ActivityThread.java:4748) 
   at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4688) 
   at android.app.ActivityThread.-wrap1(ActivityThread.java) 
   at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1405) 
   at android.os.Handler.dispatchMessage(Handler.java:102) 
   at android.os.Looper.loop(Looper.java:148) 
   at android.app.ActivityThread.main(ActivityThread.java:5417) 
   at java.lang.reflect.Method.invoke(Native Method) 
   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726) 
   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616) 
I/FirebaseInitProvider: FirebaseApp initialization successful

解決策 : 素直に失敗したことを認めましょう

社会放流されて6ヶ月経ったから云々という話

2016年4月に社会へ放流され,2016年10月現在, Quipper, Ltd.Androidエンジニアとして働いています*1

実は4月の時点ではリクルートマーケティングパートナーズ(RMP)へ入社し*2,7月からQuipperに籍を移しました。


ということで,2つの会社を経験した中で学べた良いと思う事をつらつらと書きたいと思います。
下記の意見は完全に僕個人の意見であり,所属企業や所属員を代表するものでも代弁するものでもありません。(一応)

なければ作るか,作れないならどうすればいいか等 考えることのできる広い視野を持つ

ばんくしくんの なりたいな「無ければ作るエンジニア」 に感化されたわけではなく,結構前からこの考えにかなり似た思考を持っています。
「{前例,ライブラリ}がないんだよね。だからできないよね」って正直意味がわからないし,その考えでいくと「え,じゃあ誰かがやった施策や実装だけを繰り返していくんですか?」ってなってしまう。

RMPでもQuipperでも「ないからやめよう」とかそんなことを言う先輩は一人もいなくて,もし色んな制約の上で出来ない or やらないと結論が出ても,キッチリと代替案が出て来る。
そういう人達の発想は非常に柔軟で,一緒に働いて本当に面白いです。そういうエンジニアになりたいと思います(って言えって言われました)。

あ、もちろん「無ければ作ればいいじゃん?」とそこまで安直な発言をする気はないですが,「無いなら無いなりに,作ることや代替案も含めて広い視野で考えていこうな」という風なスタンスです。
でも職場に寝る場所を作るのだけはやめようかな・・・って思ってます。

newbieな質問は恥じゃない

初歩的な質問をすることは別に恥でもなんでもないということです。

最初は「こんな質問していいのか?」と思って質問ができず,当然会議では発言できず,加えて同期も何も質問をしないとなると完全に「自分だけが分かっていない」ような気になりました。
これ,経験したことのある人がいれば分かると思うんですが,すごくきついです。特に同等っぽい立場の人(e.g. 同期)が質問をしないと完全に精神があかんことになります。

というわけで個人的には結構気にしていたんですが,思い切って「◯◯が施策に効いてくるイメージが湧かないんですが,そういう傾向があるんでしょうか?すごい初歩的な質問で申し訳ないです」と言ってみると「まあそりゃ知らないよね。説明不足でごめん!最初から順を追って説明し直します!」という感じの返答を貰えました。
周りからすれば,質問なんて気にする必要もないくらいのことだったようで,何よりも「会議で発言しない」ことの方が問題視(表現に語弊がありますが,単に目につく的な意味です)されていたようです。


まあ開き直ってみれば,そもそも関わるプロダクトの専門家として採用されたわけではないですし、既存・競合プロダクトの知識や施策への理解が足りないのは当たり前だな・・・という気持ちも,「今では」出てきたりします。今では。

スピード感を維持したまま,戦略を考えるということ

Quipperでは「おっしゃ!それやろうぜ!」的なノリでガンガン施策の方針やプロトタイプがあがってきます*3
このスピード感だけでも個人的には憧れる部分ではあり,まだ実践できていないほどにハードルの高い部分なんですが,そのノリの施策の裏にきっちりと「引き際」と「評価」が存在します。

具体的な施策をあげることは諸々の都合でできませんが,その軽いノリとは裏腹にきちんとその利点や方針,早期見積もりを出せるのは本当に尊敬の念しかありません。
軽いノリで合意をとったからといって適当なやり方で進むのではなく,ステークホルダーへの確認等はもちろん怠りませんし,プロトタイプかロードマップのどちらか(規模次第)がすぐに降ってきます。

完全に内向きの施策の話で言えば,デザイナー陣が「簡単にNative appからフィードバックしたいんだよね」というIssueを立ててKPTで話した直後,Android/iOSエンジニアと集まって即話し合い,からの即「実装してみました」のスピード感は見てて気持ちよかったです。

伝え方は文章だけじゃない

Quipperは基本英語です。といっても日本オフィス内での会話は日本語なんですが・・・Androidチームは普段の開発でも英語でのチャットが必須です。

ちなみにわしのTOEIC力は315万(/万)じゃ。っていう点数を取ったことがあるくらいには英語ができません。正直入社前は漏れそうなくらいビビってました。現在は漏れてます。

周りの人達は出来る限り分かりやすい英語を使ってくれますし、エスパーなのかな?って思うくらい僕の意味不明な英語を解読してくれます。多分エスパーなんですけど。

ですが,それに甘えるわけにもいきませんし,エスパーにも限度があります。
僕があまりにもパッパラパーなことを言うと「What do you mean?」と返されます。直訳して日本語で言われたらトイレで一人寂しく泣くことも辞さない構えです。

まあそれはさておき,(我流ですが)英語学習は当然毎日やっています。が、それがすぐ結果として現れるかというと,そうもいきません。
そこでさくっとできる対応として,図やコードを使って,文章以外の方法で伝える努力をしています。 そして今では,これは別に英語だろうが日本語だろうが,相手がなに人だろうが,表現方法の1つとして「やるべき」だと考えています。

といってもこれも受け売りというかパクリでして,隣席のどらえもん氏がSkitchでデザイン Issueの指示等を出してくれるんですが,これが本当に分かりやすい。
スクショに矢印と文字があるだけで,人は全ての事象を悟ることができます。あなたとSkitch,今すぐダウンロードって感じ。

え?この記事には画像がないって?ちょっと何言ってるかわからないです。

ステマ

RMPでは最初の3ヶ月の間でしたが、ワンオペ(意味深)、スタディサプリ ENGLISH Androidプロジェクトのテストストラテジーやポリシーの策定をやったり、他プロジェクトの手伝い、とある機能のサーバー実装とAndroid実装のほぼ全てを一人で任せて貰えたのは中々楽しい経験でした。
新卒が生意気に「プロジェクトを横断して働きたい」と無茶振りしたにも関わらずそれを実現してもらい,良い感じに評価軸まで立ててくれる会社はそうはないんじゃないかなーと思います。

Quipperでは新卒特別待遇!みたいなものはなく(多分),一人のエンジニアとして扱ってもらっています(多分)。そしてそう信じながらっょぃDev・Bizの中で働いています。
開発チームの全員が施策に対して考えをしっかり持っており,「施策はBizサイドが出すもの,Devサイドはその施策を実行するもの」という環境とは程遠いです。
当然のことながら,施策の話し合いをするとき そこにDevだからとかBizだからとかそういう変なわだかまりは一切ないです。
そんなこんなで,技術力の高さは勿論のこと,プロダクトを作るという面で非常に意識の高い人達と一緒に働き,プロダクトを作っていけるということは,新卒ぺーぺーのエンジニアにとっては非常に贅沢な機会だと身に沁みて感じます。

とりあえずRMPとQuipperはいいぞ。

おまけ

社会人になって1ヶ月が経った5月中頃、同じチームのAndroider × 2 がGoogle IOへ出張。
そのため1週間ほどぼっちAndroiderをやってました。リリースがその次の週あたりにあって、QAだけはやった気がします。

まあすごい頑張ったんだぜ、っていう話ではなく、セルフLGTMを完璧なタイミングで見られて悲しくなりました。
しかもセルフLGTMは後にも先にもその1回だけだっただけに、悲しみが深い。あと普通にぼっちで寂しかったです。

f:id:jmatsu:20161010142513j:plain

*1:スタディサプリ高校大学受験とQuipper School、Quipper VideoのAndroidアプリ開発に関わっています

*2:RMPではスタディサプリ ENGLISHAndroid,バックエンドやフロントエンドの軽いタスク、kidslyAndroidに関わっていました。

*3:RMPではそうじゃなかったという話ではなく,プロダクトのフェーズが僕の所属していた期間中にそういう段階じゃなかっただけという話です。

`Error:Build-in class shrinker and multidex are not supported yet.`

色々と開発を進めていたところ、64K問題に引っかかってしまったので Multidexを有効にしたら以下のエラーに遭遇してしまった。

Error:Build-in class shrinker and multidex are not supported yet.

文字通り build-in class shrinker と multidex は同時使用できないよという話なんだけど、build-in class shrinkerを使うようにした覚えはない。

結果から言えば 「useProguard false を明示的に呼ばない」だけで解決した。

proguard設定を作っていけば build-in class shrinker でもいいんだけれど、そうもいかない場合のために原因を一応探しておいたのでメモ。

まず、build-in class shrinkerを使うためには以下のAND条件を満たす必要があると思っていた。

  • useProguard == false
  • minifyEnabled == true

ところがそれは build-in class shrinkerによるcode minifyingを行わない状態になる、というだけであり、build-in class shrinker自体はavailableになるようだ。 このエラーはminifyする/しないを判定する以前に吐かれるらしいので、build-in class shrinkerがavailableなだけで駄目だったというわけ。

つまり「明示的にuseProguardをfalseに設定した時点で、minifyEnabledの値に関わらずbuild-in class shrinkerを利用していると認識される」ことが原因だった。

Support LibraryのPreferenceFragmentCompatでPreferenceScreenによる遷移を有効にする

TL;DR

  • AppCompatActivityはもちろん、PreferenceActivityに載せても動かない
  • 自前でハンドリングする必要があり、以下のいずれかの手法を取る必要がある。
  • ActivityにOnPreferenceStartScreenCallback や OnPreferenceStartFragmentCallback を実装させる (Activity起動 or View使い回し)
  • PreferenceFragmentCompat#onNavigateToScreen(PreferenceScreen) をoverrideする (上記をPreferenceFragmentCompat上で処理する用)
  • boolean PreferenceFragmentCompat#onPreferenceTreeClick(Preference) をoverrideする (Fragment起動用)

PreferenceFragmentCompat

Support Library v23から導入された android.support.v4.Fragment をベースにしたPreferenceFragment。
3rd party ライブラリを使って凌いでいた人も多かったはず。
そして僕が気付いたのはv24.2.0なので今更感がある。

developer.android.com

PreferenceScreenによる遷移が自動では処理されない問題

今までのPreferenceと同様、xmlによる構築が可能。
xml内にルートでないPreferenceScreenがある場合、そのPreferenceScreenをクリックすると新しい画面が開かれる仕様となっている。

公式リファレンスを見るとxmlにPreferenceScreenが書かれており、その子要素の注釈として、Next screenで表示されるものを記述できるぞ!と書いてある。

f:id:jmatsu:20160925175244p:plain

ということで、PreferenceFragmentCompatでもやってみたが、画面が開かれない・・・
でもIntentを記述するとちゃんと起動するので、click処理は諸々走っている様子。

A PreferenceScreen object should be at the top of the preference hierarchy. 
Furthermore, subsequent PreferenceScreen in the hierarchy denote a screen break -- that is 
the preferences contained within subsequent PreferenceScreen should be shown on another screen. 
The preference framework handles this by calling onNavigateToScreen(PreferenceScreen).

こうも書いてあるけど、これで「自分でハンドリングして」と読むのはちょっと厳しくない・・・?

とりあえず onNavigateToScreen はいつ呼ばれるのかというと、PreferenceScreen#onClick() で処理されていた。

    @Override
    protected void onClick() {
        if (getIntent() != null || getFragment() != null || getPreferenceCount() == 0) {
            return;
        }
        final PreferenceManager.OnNavigateToScreenListener listener =
                getPreferenceManager().getOnNavigateToScreenListener();
        if (listener != null) {
            listener.onNavigateToScreen(this);
        }
    }

ちなみに PreferenceManager#getOnNavigateToScreenListener() はPreferenceFragmentCompat自身を返し、それは OnNavigateToScreenListener を実装している。
うーん、いやしかし、これを見ると中のListenerを呼ぶ条件は満たしている。
とりあえず、Intentは別の部分で処理されていると考えて良さそうで、分けて考えて良さそう。
一個一個自分でclick listenerをbindしてもいいけど・・・それは根本解決ではないし、今後困ってしまうので探してみる。

まずPreferenceScreenをクリックしたときに呼ばれる上述のlistenerの中身は以下のようになっている.(v24.2.0)

// in PreferenceFragmentCompat.java

 /**
     * Called by
     * {@link android.support.v7.preference.PreferenceScreen#onClick()} in order to navigate to a
     * new screen of preferences. Calls
     * {@link PreferenceFragmentCompat.OnPreferenceStartScreenCallback#onPreferenceStartScreen}
     * if the target fragment or containing activity implements
     * {@link PreferenceFragmentCompat.OnPreferenceStartScreenCallback}.
     * @param preferenceScreen The {@link android.support.v7.preference.PreferenceScreen} to
     *                         navigate to.
     */
    @Override
    public void onNavigateToScreen(PreferenceScreen preferenceScreen) {
        boolean handled = false;
        if (getCallbackFragment() instanceof OnPreferenceStartScreenCallback) {
            handled = ((OnPreferenceStartScreenCallback) getCallbackFragment())
                    .onPreferenceStartScreen(this, preferenceScreen);
        }
        if (!handled && getActivity() instanceof OnPreferenceStartScreenCallback) {
            ((OnPreferenceStartScreenCallback) getActivity())
                    .onPreferenceStartScreen(this, preferenceScreen);
        }
    }

つまり以下の条件のうち一方を満たす必要がある。

  • PreferenceFragmentCompat#callbackFragment is an instance of OnPreferenceStartScreenCallback
  • PreferenceFragmentCompat#activity is an instance of OnPreferenceStartScreenCallback

で、じゃあそこらへんどうなってるんだっけ・・・っていうと

  • PreferenceFragmentCompat#getCallbackFragment() は常にnullを返す
  • Preference用のCompatActivityはないのでAppCompatActivityを使っていたが、当然実装してない

何もしてないやんけ。自前でハンドリングが必要だなぁ・・・。

とここまでやったところで記述を見つける。

f:id:jmatsu:20160925180541p:plain

・・・うぇい!

調べたら onPreferenceTreeClick と OnPreferenceStartFragmentCallback 周りも同じ感じだった。

実装方法の検討

じゃあどれが一番いいんだろう、と。
このdocによると、Activityに実装させる方法が正攻法っぽい。
他のやり方でもできそうなので色々調べてみる。

PreferenceFragmentCompat#getCallbackFragment()Javadocを見てみると

/**
     * Basically a wrapper for getParentFragment which is v17+. Used by the leanback preference lib.
     * @return Fragment to possibly use as a callback
     * @hide
     */

あー・・・なんか用途が違う。実現可能ではあるけれど、提供された目的と違う方法はあまり好ましくない。
なのでやるとしたら、まあ以下の通りになりそう。

  • ActivityにOnPreferenceStartScreenCallback や OnPreferenceStartFragmentCallback を実装させる
  • onNavigateToScreen(PreferenceScreen) をoverrideする
  • boolean onPreferenceTreeClick(Preference) をoverrideする
  • getCallbackFragment() をoverrideして、OnPreferenceStartScreenCallback や OnPreferenceStartFragmentCallback を実装したFragmentを返すようにする (本来の用途と違うのでやめた方がいい)

実装にあたって

気をつけないといけないことはいくつかあるが、PreferenceScreen はParcelableでもなんでもないということを念頭に置く必要がある。
つまりそのPreferenceScreenをそのまま新しく作成するPreferenceFragmentCompatのインスタンスに渡して云々・・・は出来ない。

備忘録も兼ねて、いくつか思いついた実装を書いておく。

OnPreferenceStartScreenCallback

  • Fragment, Intentが設定されておらず、そのPreferenceScreenは1個以上の子要素を持つことが保証されている。

xmlに書いたPreferenceScreen(子孫)に表示したい全ての要素が記述してあるとき

PreferenceFragmentCompatを再利用する
boolean onPreferenceStartScreen(caller: PreferenceFragmentCompat, pref: PreferenceScreen) {
    if (pref.getKey() == null) {
        return false;
    }

    caller.setPreferencesFromResource(R.xml.setting, pref.key);
    return true;    
}

どこからか、利用されているxmlのidを拾ってくる必要はあるが、PreferenceFragmentCompatの使い回しができる。
けどback処理が面倒くさそう。

sample: https://gist.github.com/jmatsu/f7a7f0a8384c84e50ea5211e1207bd8c#file-reuse_preferencefragmentcompat-kt

PreferenceFragmentCompatを新しく作る
class HogeFragment extends PreferenceFragmentCompat {
    static PreferenceFragmentCompat newInstance(String rootKey) {
        PreferenceFragmentCompat fragment = new PreferenceFragmentCompat();
        Bundle bundle = new Bundle();
        bundle.putString(PreferenceFragmentCompat.ARG_PREFERENCE_ROOT, rootKey);
        fragment.setArguments(bundle);
        return fragment;
    }

    void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
        setPreferencesFromResource(R.xml.setting, rootKey)
    }
}

// ↓ Activityで実装する

boolean onPreferenceStartScreen(caller: PreferenceFragmentCompat, pref: PreferenceScreen) {
    if (pref.getKey() == null) {
        return false;
    }

    getSupportFragmentManager().beginTransaction().replace(R.id.container, HogeFragment.newInstance(pref.key)).addToBackstack(null).commit();
    return true;    
}

どんなFragmentで扱うかを知っている必要がある。Fragment attributeを設定してしまうと、ここが呼ばれないことを留意する必要がある。

sample: https://gist.github.com/jmatsu/f7a7f0a8384c84e50ea5211e1207bd8c#file-renew_preferencefragmentcompat-kt

PreferenceScreenのkeyとxmlのid名を対応させる

boolean onPreferenceStartScreen(caller: PreferenceFragmentCompat, pref: PreferenceScreen) {
    if (pref.getKey() == null) {
        return false;
    }

    int id = getResource().getIdentifier(pref.getKey(), "xml", getPackageName());
    caller.addPreferencesFromResource(id); // lint抑制をする
    return true;    
}

ゴリ押し。PreferenceFragmentCompatの使い回しができる。けどやっぱりback処理が面倒くさそう。

試してないけど。

OnPreferenceStartFragmentCallback

  • Fragmentが設定されていることが保証されている。

Fragment名から愚直に作成する

boolean onPreferenceStartFragment(caller: PreferenceFragmentCompat, pref: Preference) {
    if (!(pref instanceof ScreenPreference)) {
        return false;
    }

    String fragmentName = pref.getFragment();
    // name に合わせてFragmentを生成して色々する
    return true;    
}

普通っぽい。

Android上でANTLR4がこける問題。

GraphQLライブラリのgraphql-javaAndroidで動かそうとしたらANTLR4周りでこけた。

f:id:jmatsu:20160917214736j:plain

dexOptions.preDexLibraries = false で直るよ!とかあったけど試したらエラーが増えたし、これで直るならcleanでも直りそうなもんですよね・・・

結局ANTLR4のバージョンが低くてgui依存周りがこけてるだけだったので、ANTLR4を更新しておけば解決した。

ref: https://github.com/antlr/antlr4/issues/1160

compile('com.graphql-java:graphql-java:2.1.0') {
    exclude group: 'org.antlr'
}
compile group: 'org.antlr', name: 'antlr4-runtime', version: '4.5.3'

でも結局deserializeに利用できそうになく、とりあえず依存から廃した。 一応備忘録的な感じで残しておく。

compile('com.graphql-java:graphql-java:2.1.0')