Java8から使えるようになったラムダ式はAndroidではそのままでは使えません。Android Studioが「ラムダ式で書いたらこうなる」と見た目だけ表示してくれたりしますが、実際にラムダ式でコードが記述されているわけではありません。
例えば、このようなボタンにクリックリスナーを設定するコードがあったとして、
mButton.setOnClickListener(new View.OnClickListener(){ @Override public void onClick(View v){ Log.d(TAG, "Click!!"); } }); Android Studioがラムダ式スタイルで見た目をすっきりさせてくれるわけです。
mButton.setOnClickListener((v) -> { Log.d(TAG, "Click!!"); }); これはエディタ上で折りたたまれて表示されているだけで、実際に記述されているコードは上のものです(マウスカーソルを上に持って行くと表示されます)。
で、これを本当にラムダ式で記述できるようにするライブラリとしてretrolambdaというものがあります。あるんですが、その前に、そもそも私はラムダ式自体がよく分かっていません。そこでまずは、ラムダ式とはなんぞやというところから調べることにしました。
ざっくりした書き方で、私の中のイメージを書き連ねたのでわかりにくいところがあると思います。間違ってるところもあると思いますが、その際はご指摘いただけるとうれしいです。(長い前置き終わり)
ラムダ式はいろいろ省略することのできる記法 ラムダ式はアロー演算子を利用して(引数) -> {処理}という書き方ができるものです。この書き方ができるのは一定の条件下においてですが、引数にOnClickListenerのような抽象メソッドが1つだけのinterfaceを取る場合と考えておけばいいと思います。
OnClickListenerは以下のように、onClickという抽象メソッドが1つだけ定義されたインターフェースです。
public interface OnClickListener { /** * Called when a view has been clicked. * * @param v The view that was clicked. */ void onClick(View v); } これをsetOnClickListener()する際に、無名クラスとして定義して使っているわけですが、その無名クラスの定義をすっ飛ばして、直接抽象メソッドへの引数と処理だけを書くことができるのです。
なぜ省略できるか なぜ省略して書けるかというと、まずsetOnClickListener()というメソッドは、引数にOnClickListenerというインターフェースをとります。そしてJavaのinterfaceという仕組みによって、onClick()というメソッドが必ず実装されていることが保障されます。すなわち、この中では少なくともonClick()というメソッドが呼ばれることがわかっているわけです。だから省略できるのです。
ラムダ式の左辺については、引数が1つであれば()を省略できたり、型を省略することができます。型を省略できる理由は、インターフェースの定義で型が決められているため、省略されても分かるからです。
右辺の{}はメソッドの中身が1文ですむ場合に省略可能です。また、return文だけですむ場合も同様で、さらにreturn句も省略できます。なぜならソッドの戻り値がインターフェースの定義で決められており、右辺の処理が戻り値を表していると自明だからです。
あくまでこれらは「省略できる」であって、別に省略せずに書いても問題ありません。(もっともわざわざラムダ式を使う目的を考えれば、省略できるところは省略すべきでしょうが)
それでも分からない人に、もしかしたら引っかかるかもしれない情報 省略して書けることは分かったけど、やっぱりラムダ式よく分からない。そんな人は、「そもそもなぜメソッド1つだけが定義されたinterfaceを用意して使っているのか」が分かっていないことが原因かもしれません(私はそうでした)。
ラムダ式が適用できるパターンが全てそうかは知りませんが、少なくともOnClickListenerについてはObserverパターンによる実装です。なぜメソッド1つだけのinterfaceを定義して使うかというと、「クリックされた」というイベントを監視するのに便利だからです。
イベントの発生を監視するためには、イベントの発生を通知する人が必要になります。そのときに監視側が「俺はnowClickメソッドで通知してくれ」、「僕にはclickメソッドで教えて」、「私はonClickメソッドで」とバラバラな実装になっていたらどうでしょう。こうなるとイベントを通知するクラスは、イベントを監視するクラスが増える度に通知処理を書き換える必要性に追われます。そんなのはナンセンスです。
そこで「うるせー! クリックイベントは今後OnClickListenerというインターフェースのonClickメソッドで通知する。異論は認めん!」と通知する側が決めてしまえば全てがすっきりします。
イベント通知側は監視者が誰であるかを気にする必要がなくなります。どんなクラスが通知を受けたがっているのか、どんなメソッドで通知して欲しいのかを考えなくてもよくなるのです。イベントが発生したときにインターフェースを介して通知すればいいだけなのですから。
受け取る側は、指定されたインターフェースを実装しさえすれば、イベント発生時にそのメソッドを通じてイベントの発生を検知することができるわけです。誰がイベントを通知してくるのかを考えなくても良くなります。実際、AndroidにおいてonClickメソッドを誰が通知してくるのか考えなくても処理できています(Androidがよしなにやってくれてるんでしょう)。
それをスマートに実現する方法が、メソッド1つだけ定義したinterfaceを使うことです。
私自身まだふわっとした理解なので、余計に混乱させてしまったら申し訳ないです・・・。
まとめ ラムダ式は省略記法 OnClickListenerのような抽象メソッド1つのインターフェースで使える そういうインターフェースはObserverパターンで使われる なぜならイベントの発生・監視に便利だから ラムダは単なる省略記法というわけではないのでしょうが、とりあえず今は単純に割りきって考えようと思います。単なる省略記法と思えば、ラムダ式への心理的なハードルがぐっと下がりました。最初の一歩としては「省略できるんだ」でいいんじゃないかと思います。ただし変な覚え方して後々困ることになるのかもしれませんが・・・。
ちなみにそんなラムダ式をAndroid Studioで使えるようにするライブラリが、gradle-retrolambda – GitHubです。(導入の仕方とかはここでは語りません、よく分かってないので)
参考 知っといてムダにならない、Java SE 8の肝となるラムダ式の基本文法 (1/3)
矢沢久雄の早わかりGoFデザインパターン(6) 第6回 Stateパターン/Observerパターン
RxAndroidとRetrolambdaで大体Java8をAndroidに持ち込む
AppBar(Toolbar、ActionBar)の部分が大きめの画像になっていて、コンテンツをスクロールするとそれに合わせて画像が縮んでいき、最終的にToolbarだけが残る(もしくは全部隠れる)みたいなデザインがありますよね。あれを実装しようと思って試行錯誤してみました。
試行錯誤になってしまった原因は、スクロール可能なコンテンツ部分を横着してListViewで作ってしまったからでした。見かけるサンプルはだいたいRecyclerViewを使っていたのですが、使ったことがないため使い慣れているListViewでやろうとしたのが間違いでした。
ListViewで実装すると、ListViewをスクロールしてもAppBarは連動して動いてくれません。AppBarの部分をスクロールすると伸縮してはくれますが、巷にあふれるパララックスAppBarはこんな残念な動きはしていません。
コードで何か手を加えないといけないのだろうかと調べるうちに、なぜListViewではAppBarが連動して動かないのか原因が分かりました。今回はそのお話です。
Patterns– Scrolling techniques
layout.xmlの設定 基本的にパララックスなAppBarを実装するには、レイアウトXMLの記述のみで実装できます。
サンプルコード – GitHub
このMaterial Design(Android desgin support library)による階層構造を初めて見ると、なんだかややこしく感じてしまいますが、1つずつ紐解いていけばそう難しい構造ではありません。
正確にはandroid.support.design.widget.〜とFQCN(パッケージ名を含めたクラス指定)になりますが、ここでは長くなるので省略しています。
CoordinatorLayout ├AppBarLayout │└CollapsingToolbarLayout │ ├ImageView │ └Toolbar ├ListView(などスクロール可能なコンテンツ) └FABなどお好みで 基本的にXML上でちゃんと必要な指定さえ行えば動きます。コードは不要です。
CoordinatorLayout 今回の例ではListViewのスクロールにあわせてAppBarLayoutを伸縮させるために存在しています(FABをToolbarとListViewの中間に配置する役割も担っていますが)。このCoordinatorLayout自体は内包したView同士を連携させたりする単なる入れ物です。全然「単なる」ではないですけど。
AppBarLayout AppBar部分のLayoutを管理するコンテナで、AppBarの部分に表示するViewをこの中に入れてやります。Blank Activityを作成すると、この中にはToolbarだけが入っていると思います。
ここではAppBarの高さを指定してやります。android:layout_height="192dp"。
CollapsingToolbarLayout 折りたためるToolbarのための入れ物です。スクロールによるAppBarの動き方を指定することができます。ここではapp:layout_scrollFlags="scroll|exitUntilCollapsed"と指定しています。
ImageView AppBarが全開のときに表示されるイメージ画像です。コンテンツのスクロールに合わせて縮み、最終的にToolbarだけが残ります。ここではapp:layout_collapseMode="parallax"を指定しています。
Toolbar Toolbarです。ここではapp:layout_collapseMode="pin"を指定しています。この指定でToolbar自体は隠れずに残ります。
ListView よく見かけるサンプルではRecyclerViewやNestedScrollViewが利用されています。しかし私はRecyclerViewの使い方がよくわからなかったのでListViewで代用しています。
ここでは必ずapp:layout_behavior="@string/appbar_scrolling_view_behavior"の指定が必要です。
この@string/〜はAndroid Support Libraryのstringリソースを参照していて、その中身はandroid.support.design.widget.AppBarLayout$ScrollingViewBehaviorとなっています。つまりこのListViewの振る舞いとして、AppBarLayoutのScrollingViewBehaviorを指定しているわけです。
ListViewを使うと、そのままではListViewがスクロールされるだけでAppBarが伸縮しません。ListViewのスクロールと連動させるためには、ListViewにandroid:nestedScrollingEnabled="true"を指定する必要があります。
なぜか。スクロール可能なコンテンツとAppBarの伸縮を連携させるためには、ListViewがスクロールされたということをCoordinatorLayoutに伝える必要があります。RecyclerViewやNestedScrollViewは標準でこれをやってくれるわけですが、ListViewは何もしません。そこでCoordinatorLayoutにスクロールイベントを通知するための設定を有効にしてやる必要があるのです。
android:nestedScrollingEnabled="true"(NestedScrollに関する処理)はAPI21以上のViewに実装されています。
余談:なぜAppBarが動くのか 仕組みを完全に理解したわけではないので、ざっくりとした説明です。
ListViewの上でスクロールを行うと、ListViewの中身がスクロールされます。これはListViewのonTouchEventで処理されています。これだけではListViewの中でスクロールイベントが処理されるだけで、AppBarの変形にはつながりません。
そこで登場するのがCoordinatorLayoutです。こいつが子Viewのスクロールと、別の子Viewを連携させるわけです。
連携させるためにはCoordinatorLayoutにスクロールイベントを通知する必要があり、その仕組がNestedScrollです。RecyclerViewやNestedScrollViewは初めからCoordinatorLayoutと連携する前提で作られていますし、ListViewなどでもSDK21からNestedScrollに関する処理が追加されています。ただし初期状態では無効化されているので、NestedScrollの処理を有効化してやる必要があり、それがandroid:nestedScrollingEnabled="true"になります。
NestedScrollの処理は子ViewのonTouchEvent(onTouchMove)でCoordinatorLayoutに伝わります。CoodinatorLayoutはonNestedPreScroll内でBehaviorが設定されている子Viewを探し、見つかったBehaviorに対してdispatchOnDependentViewChangedを呼び出します。今回の例ではScrollingViewBehaviorです。
最終的にAppBarのサイズを伸縮させる処理は、このBehaviorのonDependentViewChangedで行われているみたいです。
参考 Handling Scrolls with CoordinatorLayout
app/build.gradleのdependenciesにさり気なく書いてあるサポートライブラリ。
dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:22.2.1' } appcompat-v7:22.2.1のこの22.2.1の部分。自分で指定しようと思うと、バージョンいくつが存在しているのかが分からず、どうやって確認すればいいのかも分かりません。とりあえず最新のものが当たればいいやと、+を使ってごまかしたりしてきましたが、最新のものが当たるとそれはそれでうまくいかないことがあったりします(23が出てるんだけど、とりあえずはtargetSDKを22で作ってる今とか)。
要するに、1つ前のバージョンを指定したいのだけど、その1つ前のバージョンというのはいったいいくつなんだというのが困ります。結論から言うと22.2.1なんですけど、じゃあそれをどうやって調べたらいいのかという話です。
Support Library – Android Developersで確認できます。
また、パソコンにインストールしているAndroid SDKのディレクトリを潜って行くことでも調べることはできます。
(Android SDKのインストールディレクトリ)/extras/android/m2repository/com/android/support/appcompat-v7 このディレクトリに、バージョンごとのフォルダがあるのでそこでも確認可能です。
そもそも指定できるバージョンの候補を出してくれると楽なんですけどね。
ゲーム開発エンジン使ってアプリを作ってみたいなとは以前から考えていましたが、今回cocos2d-xの勉強をしてみました。
他にもUnityがあって、しかもそちらの方が情報量も書籍の数も多いです。にも関わらず、なぜcocos2d-xを選んだかというと、私には3次元がややこしかったからです。UnityはLive2Dと連携させるのにちょっと触りましたが、私には合いそうもないというのが第一印象でした。cocos2d-xなら2次元だから比較的入りやすいんじゃないかなと思ったのです。
実際にやってみたら、cocosは情報が少ないという意味でハードルが高くて困っていますけどね。もっとも、Unityもハウツー本使って勉強してみると意外と簡単にできるかもしれません。いずれやってみようと思います。
今回勉強するにあたって利用した本はこちらです。
cocos2d-xではじめるスマートフォンゲーム開発 [cocos2d-x Ver.3対応] for iOS/Android posted with ヨメレバ 三木 康暉 技術評論社 2014-12-27 Amazon Kindle 楽天ブックス 紀伊國屋書店 書籍で扱われているcocos2d-xのバージョンはは3.2ですが、私は3.6(勉強開始当初の最新、現在は3.7が出ている)でやりました。 Android Studioとの連携 Android Studioでcocosのプロジェクトを開くにはどうしたらいいかという問題ですが、cocos2d-xのバージョン3.7からAndroid Studio用のプロジェクトが作成されるようになっています。ですので、3.7を使えば解決されます。さよならEclipse。
といっても、あくまでプロジェクトが開けるというだけで、Android Studioだけを使ってC++のコーディングなども含めて開発していけるわけではありません(多分)。素直にMacならXcode使ってiOS用に作っていく、WindowsならVisual Studio使ってWindows Phone向けで作っていくのがいいと思います。(両者であればプロジェクトを開く→実行する→ちゃんと動くので)
Android Studio使って進めていきたかったのですが、今回はcocos2d-xの勉強を優先することにしました。
3.6で本の内容をひと通りやってみて 基本的には本に書いてある内容は、バージョン3.6でもそのまま動きます。
ただ1点だけ、5章のP204ページにあるコードは修正しないと動きませんでした。
auto properties = _tiledMap->getPropertiesForGID(gid).asValueMap(); このasValueMap()のところの型チェックでプログラムが止まってしまいます。これはcocos2d-xのバージョンによる問題ではなく、Tiled Map Editorのバージョンが違うせいで動かなかったのかもしれません(本のTiled Map Editorのバージョンは0.9.1、私が使ったのは0.12.3)。
auto property = _tiledMap->getPropertiesForGID(gid); if (property.isNull() || property.getType() != Value::Type::MAP) { return nullptr; } auto properties = property.asValueMap(); 一部、本の通りにやっても画面にうまく表示されない箇所があるにはあるのですが、これはcocos2d-xのバージョンによる問題なのかは分かりません。
6章のCocos Studioのあたりは参考程度に これは本のせいではありませんが、第6章のCocos Studioを使うあたりの話はそのまま利用できませんでした。というのも、本で使っているCocos Studio1.6が手に入らなかったからです。
公式サイトには2系しか公開されていませんでした。もしかしたらどこかに1.6置いてあるのかもしれませんが、私には見つけられませんでした。
Cocos Studioはバージョン2から全く別物のツールというくらいに変わってしまっています。1.6の頃にあったツールがなくなっているあたり、バージョンが上がったようにはまったく思えないです。
触ってみた限り、前にあった機能が削減されたというよりは、Cocos Studioで扱う部品がシンプルになり、もっと簡単にcocos2d-xのプロジェクトに取り込めるようになったというのが正しい認識のように思います。GUI Componentかどうかなどを意識することなく、単にNode(もしくはLayer)として扱えばいいだけなのでしょう、おそらく。
大体雰囲気で乗り切れましたが、AtlasLabelの扱いが変わっていることだけは注意が必要でしょう。AtlasLabelは画像で数字のフォントを用意すれば、そのまま数値として利用できるものです。
バージョン1.6までは文字列が何から始まるかを指定してやる必要があったようです(本を見る限りは)。
しかし2系からはAtlasLabelは必ず./0123456789という順番で画像を用意しなければならないようです。ドットやスラッシュを使わないとしても、です。文字の横幅はすべて同じにしなければなりません。要するに、Cocos Studioが渡された画像を勝手に12等分して、左から順番に./0~9と認識してくれるみたいです。サンプル素材の画像データは0〜9しかないので、画像編集ソフトを使って左側に文字2つ分の余白を入れたらうまくできました。
cocos2d-x入門書として優秀 私はC++も含めて全くの初学でしたが、本の通りやったらちゃんとできました。おかげでcocos2d-xの雰囲気は掴めた気がします。
一方でトラブったときにちょっと困ります。例えばノードを画面に追加しても描画されないとき、いったいどこを確認したらいいのかがさっぱり分かりません。
私はC++はじめて、Xcode触るのも初めてなので、うまくいかないときにどうやって原因を切り分けていけばよいのかが分からなくて困りました。というか困っています。
ですが、「こうやったら動くよ」という情報自体がありがたい存在なので、cocos2d-x触ってみたいなぁという人にはおすすめできると思います。
開発者オプションチェッカーという名前ですが、現在のところ「アクティビティを保持しない」をONにしてるかどうかをチェックするだけのアプリです。
名前の通りAndroidアプリ開発者以外の人には無用の長物です。もしかしたら、私以外に欲しいと思っている人はいないかもしれません。
アピールポイントは、設定値の監視を行う割に、特別なパーミッションを要求しないという潔さがあるところです。
作った経緯 「アクティビティを保持しない」は、画面回転でアプリが突然の死を迎えないかチェックするのに重宝します。
一方で、これを有効にした状態だとActivityの通常のライフサイクルが確認できなくなります。普通ならホーム画面を出すと、ActivityはonStopで止まりますが、有効にしていると必ずonDestroyまでいって破棄されてしまいます。この状態では、例えばLoaderのレジューム処理が確認できなくなります。(というかこれが開発のきっかけでした)
「アクティビティを保持しない」を有効にしているかどうかは、渡しの場合だと通知領域を2回スワイプ→設定を表示→開発者オプションを表示→アクティビティを保持しないの状態を確認という操作を経ないと確認できませんでした。これがものすごく面倒くさい。
だったら通知などで「アクティビティを保持しない」の状態が分かれば便利なんじゃないかなと思って作りました。
本当は、アプリから「アクティビティを保持しない」の設定を切り替えられたら良かったのですが、設定値をアプリから切り替えることは不可能でした。設定値を書き換えるには特殊なパーミッションが必要であり、そのパーミッションを有効にできるのはシステムアプリのみという制限があったからです。
そのためこのアプリでは、「アクティビティを保持しない」の設定値を読み取ることしかできません。
このアプリを作って学んだこと このアプリでService,AlarmManager,Widgetを初めて利用しました。ちょっとした思いつきの割に、いろいろ勉強になったなぁと思います。
もっとも、ウィジェットの更新時に一旦ボイスレコーダーのアイコンが表示されて、パッと更新されないのがよく分からないところです。
このアプリでできること アクティビティを保持しないが有効になってるかどうかを知ることができる、ただそれだけです。
仕組みとしては、Serviceを常駐させて設定値の変更を監視させています。設定が変更されれば通知を表示させたりウィジェットを更新させたりしているだけです。
アプリを立ち上げれば現在の設定値を常に確認できるのですが、それではアプリを立ち上げなければ有効かどうかを確認することが出来ません。それはそれで手間です。
それを実現するには、私の頭ではServiceを常駐させる以外に手段が思いつきませんでした。
Serviceの常駐は、端末のリソースを専有し続けるためバッテリーへの影響がある、何をしているか分からないなどから、あまり気持ちのいいものではないと思います。そこで、常駐しないようにするオプションも設けました。
「アクティビティを保持しない」を有効にしているかどうかを知りたい頻度というのは人によって違うと思います。1日おきくらいで構わないという人もいれば、常に最新の状態で知っておきたいという人もいるかもしれません。
Serviceを常駐させていれば、「アクティビティを保持しない」の設定値を切り替えた瞬間に、その設定値が通知されるようになっています。これを無効にした場合、設定値を切り替えてもすぐには通知されなくなりますし、ウィジェットや通知で表示されている設定値と、今現在の「アクティビティを保持しない」の設定値が異なる場合があります。
Serviceを無効化すると、設定値の変更が検出できません。そのため一定間隔で設定値を確認するしか方法が思いつきませんでした。ウィジェットであれば、「アクティビティを保持しない」の設定値を切り替えてウィジェットの更新時間が来るまでの間は、古い状態が表示されてしまうことになります。
この辺り、何かしらうまい手段があればいいのになぁと思うのですが、何かいい方法があるのでしょうか。(設定値の変更がBroadcastされてればよかったのにとは思いました)
もっとも、Broadcastがシステムによって行われていたとしても、そのBroadcastを受け取るにはServiceなりを立ち上げてBroadcastReceiverを動かしていないと受け取れないのですけどね。
要望募集中 現在バージョン0.1です。開発者オプションチェッカーという名前の割に、「アクティビティを保持しない」しかチェックしないからというのがその理由です。
他の開発者オプションは、そもそも利用していないか、有効にしていたら画面の表示で有効であることが丸わかりなものが多いので、とりあえずはこれだけでいいかとリリースしました。
「こういう設定値もチェックしたい」という要望があればコメント貰えると嬉しいなと思います。
そもそも需要があるのかどうかすら謎ですが・・・。
Android Studioのコード補完機能を過信しすぎてはよくないというお話です。
例えば、AndroidManifest.xmlにpermissionを追加するときのことです。<uses-permission android:name="android.permission.INTERNET"/>を追加しようとしたさいに、Android Studioのコード補完に任せると<uses-permission android:name="ANDROID.PERMISSION.INTERNET"/>となることはないでしょうか。しかもAndroid Studioで補完された文字列だから、大文字のままでも問題ないのかなと放置していないでしょうか。
実はこれが全部大文字になっているとうまく動きません。android.permissionの部分は小文字でなければなりません。
原因と対策 これはコード補完で候補を絞り込む際に、INTERNETの部分を大文字でタイピングしたときに起こります。最初の1文字目を大文字でタイピングすることで、選んだ選択候補の文字列がすべて大文字で入力されてしまうようです。
この対策は実はとても簡単なことで、internetと小文字でタイピングして絞り込むことです。
INTERNETの部分が大文字だから大文字で入力しなければならないと思い込んでいないでしょうか。私は思い込んでました。なぜなら、コード補完で絞り込む対象が定数である場合、少なくとも最初の1文字目は大文字でタイピングしないと絞り込めなかったからです。だからpermissionでも同じなのだろうと思ってました。
しかし、このpermissionの場合は小文字でタイピングしても絞り込めます。そして、小文字で絞り込んだ場合だと正しくandroid.permission.INTERNETと入力されます。
定数だとなぜ大文字で入力しなければ絞り込めないか これはAndroid Studioの設定によるものです。デフォルト設定がどうなのかはよく知らないので、人によって違うかもしれません。
Android Studioのコード補完機能の設定で、大文字・小文字を区別する設定があります(Preference > Editor > General > Code Completion)。私の場合はここが「First letter」になっています。そのため補完候補の最初の1文字目だけは大文字・小文字が区別されてしまうのです。
そのため定数(例えばREQUEST_CODEといったもの)の場合、少なくとも最初のRだけは大文字で入力しなければ候補に残りません。一方でandroid.permission.INTERNETの場合は最初の1文字目が小文字であるため、INTERNETの部分で絞込をする場合に入力する文字は小文字でも問題ないのです。
今のところ私が遭遇して実際に影響を受けたのはAndroidManifest.xml上でだけですが、他の部分でも影響があるのかもしれません。
Google Playで公開しているカスタムフォトウォッチで初めてコメントをもらいました。
英語でのコメントだったのですが、「12時間表示のオプションが欲しい」っていう内容でした。
おそらく私は、単にそれだけのコメントでも嬉しかったと思います。自分で作ったものに対して反応がもらえるっていうのは、それだけでうれしいのです。
しかし今回のコメントはさらに嬉しい要素がありました。「12時間表示が必要だからアンインストールしたけど、時刻や日付の色やサイズを変えれるのはとてもいい」と褒めてくれていました。
英語でのコメントでしたけど、褒めてくれてるっぽくてテンションが上りました。
つたない英語力とGoogle Translateの力を借りつつ、コメントくれた人とメッセージのやり取りしました。思えば生まれて初めて海外の人とコミュニケーションした気がします。
12時間表示は実は奥が深い 今回の話は「単に嬉しかった」だけに終わりません。実はいい勉強にもなりました。それは12時間表示についてです。
今回もらったコメントに対して、さっそく12時間表示への対応を行いました。スマホの時刻設定で12時間表示にしている場合、wear側での表示を12時間表示にするようにしたのです。12時になったら0時と表示するようにしたわけです。
しかしコメントをくれた方とのやりとりの中で、「うちの国では0時は深夜の0時(24時)を意味するんだ」ということを言われました。
カスタムフォトウォッチでは日付の表示方法についてはいろいろなパターンを提供するようにしていました。海外では月/日ではなく、日/月と表示する場合もあることは知っていたからです。
しかし時刻の表示についても似たような問題があることを、ここにきて初めて知りました。
アプリの海外対応は、文字面だけを英語にするだけではないのだなと、とてもいい勉強になりました。
とりあえず公開してしまえも必要なのかも アプリを公開するにあたって、「こんな機能も要るかな」とかいろいろ迷ったりもしました。色の選択肢はもっと増やしたほうがいいのかとか、デザインはこれでいいのかとか。
それでも最終的には「ぐだぐだ考えるの面倒くさくなったからもういいや」でアプリを公開しました。いつまでたっても公開できる気がしないし、何のリアクションももらえない状態で開発するのもモチベーションが保てなかったのです。
全てのユーザが今回の方のように丁寧なレビューをくれるとは限りません。それどころか「何やねんこの糞アプリ」みたいなコメントが寄せられるかもしれません。が、それでも公開しなければ何の反応ももらえません。
作るところまで作ったら、後は利用者の反応を見ながら開発するっていうのもいいのかもしれません。
コメント付きレビューをもらえたのが初めてだったので、なんか無性に嬉しかったっていう話でした。
greenrobot/EventBus – GitHubを使ってみました。
異なるスレッドからのイベントの通知でもうまくハンドリングしてくれるので、AsyncTaskLoaderでProgressを通知するのにも利用できます。
Broadcastを使って実装するのと比較するとコードがシンプルになって良いです。IntentFilterやIntentにデータを埋め込む際に使うキー文字列を定義したりしなくて済みます。
更に独自のオブジェクトをイベントとして渡すこともできるので、Broadcastでは難しいイベントの通知も簡単に出来ます。
準備 /app/build.gradleのdependenciesにcompile 'de.greenrobot:eventbus:2.4.0'と、1行追加するだけで使えます。
イベントの送信 イベントを送信したいところで、EventBus.getDefault().post("イベント発生");とするだけです。
これは単にStringオブジェクトを渡しているだけですが、独自オブジェクトを定義して渡すことも可能です。
イベントの受信 イベントの購読・解除 Activityでイベントを受信するなら、onResume()でEventBus.getDefault().register(this);とすることでイベントの購読を行います。
この際、onPause()でEventBus.getDefault().unregister(this);で購読解除を忘れないように。
イベントのハンドリング AsyncTaskLoaderからのイベントを受信してUIを更新するなら、onEventMainThread()をActivityに実装します。
public void onEventMainThread(String event){ mTextView.setText(event); } 送信するイベントのオブジェクトごとにこのメソッドを用意してやる必要があります。例えば他にMyEventという独自オブジェクトがある場合、別途public void onEventMainThread(MyEvent event){}を実装してやります。
メソッドをオーバーライドするわけではないので、コード補完は効きません。タイポするとこんな例外が起きてアプリが落ちます。
java.lang.RuntimeException: Unable to start activity ComponentInfo{jp.gcreate.sample.asynctaskloadersample/jp.gcreate.sample.asynctaskloadersample.MainActivity}: de.greenrobot.event.EventBusException: Illegal onEvent method, check for typos: public void jp.gcreate.sample.asynctaskloadersample.MainActivity.onEventBackgroundThrad(jp.gcreate.sample.asynctaskloadersample.MyEvent)
まとめ AsyncTaskLoaderのProgressの通知で使ってみましたが、とても便利だなと思いました。
内部的にはHandlerを使ってイベントのハンドリングを行っているみたいでした。Handlerの具体的な使用例としていい勉強にもなって、個人的には一石二鳥な感じです。ありがたや〜。
便利な半面、無計画に使うとイベントが乱立してカオスな事になりそうなので、実際に使うときには気を付けないといけないなと思いました。
AsyncTaskLoaderにはAsyncTaskのpublishProgress()のような途中経過を通知するメソッドが標準では用意されていません。
そこでブロードキャストを利用してこれを実装します。
Context#sendBroadcast()を使ってもいいのですが、これだと自分のアプリ外にもブロードキャストが送信されてしまうので、LocalBroadcastManagerを利用します。
AsyncTaskLoaderでの処理 AsyncTaskLoader側ではloadInBackground()内で、ブロードキャストの送信を行うだけです。
この際、途中経過のデータはIntentに埋め込んで送信する必要があります。
@Override public String loadInBackground(){ //非同期処理 Intent intent = new Intent(MainActivity.ACTION_PROGRESS) .putExtra("key", "hoge"); LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent); } MainActivity.ACTION_PROGRESSはインテントフィルタを表す文字列です。
Activity側の実装 //インテントフィルタの定義 public static final String ACTION_PROGRESS = "jp.gcreate.sample.asynctaskloadersample.ACTION_PROGRESS"; //ブロードキャストレシーバーの作成 private BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String progress = intent.getStringExtra("key"); mTextView.setText(progress); } }; @Override protected void onStart() {
super.onStart(); //ブロードキャストレシーバーの登録 LocalBroadcastManager manager = LocalBroadcastManager.getInstance(this); manager.registerReceiver(mReceiver, new IntentFilter(ACTION_PROGRESS)); } @Override protected void onStop() { super.onStop(); //ブロードキャストレシーバーの解除 LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver); } ブロードキャストレシーバーを作成して、ここでAsyncTaskLoaderから送られてくるProgressを受け取りとUIの更新処理を実装します。
後はonStart()でブロードキャストレシーバーの登録、onStop()で解除を行ってやればOKです。
細かいサンプルはGitHubにおいてます。
基本的にはここに書いてあるとおりにやればいいだけの話です。
準備 /app/build.gradleのdependenciesにjunitを追加します。
testCompile 'junit:junit:4.12' testCompile 'org.mockito:mockito-core:1.9.5' この際に注意が必要なのは、androidTestCompileとtestCompileは別物であるということです。
何が別物かというと、テストコードを配置するディレクトリがそれぞれ違います。
その名の通りandroidTestCompileはandroidTestディレクトリに配置したテストコードのコンパイル時にだけ使うライブラリです。同じくtestCompileはtestディレクトリに配置したテストコードのみに使われるライブラリになります。
なお、androidTestディレクトリは自動的に作成されていますが、testディレクトリは自分で作らなければなりません。(ディレクトリは/app/src/test/java/パッケージ名にしてやればOK)
androidTestとtestの切り替え Build Variantsウィンドウを開くと、Test Artifactという欄があります。
Android Instrumentation Testsを選択していると、androidTestディレクトリ以下にあるテストコードが有効化されます。有効化されるというのが適切なのかは分かりませんが、Android Studioからコンパイル対象のソースコードであると認識されます。
Unit Testsに切り替えると、testディレクトリが有効化されます。試しに切り替えてみると、androidTestディレクトリの色が変わって、テストコードのアイコンに赤いJアイコンが出るようになると思います。
IDE上からテストを実行しようと思うと、このBuild Variantsをいちいち切り替えないといけないのが面倒くさいかもしれません。
しかし、ターミナルからGradleを使って実行する場合は、このTest Artifactsの切り替えはしなくてもいいみたいです。Gradleからテストを実行する場合、./gradlew connectedAndroidTestがAndroid Instrumentation Testsを、./gradlew testがUnit Testsを選択してテストを実行するのと同じになります。この場合のテスト結果は/app/build/reports/testsの中に出力されます。
テストコードはViewやActivityなどのUIに関するテストをandroidTestディレクトリに、純粋なJavaコードのテストはtestディレクトリに置くように工夫すべきでしょう。そうしてやれば、ユニットテストにかかる時間を削減できて幸せになれると思います。