TextViewに設定したテキスト内のURLに遷移する

TextViewに設定したテキスト内にURLがあった場合に、そのリンクをクリックできるようにしたい。 クリックしたらブラウザが開いて該当ページに移動できるようにしたい。

手っ取り早くこの要望を満たそうと思ったら、TextViewには便利な機能が用意されている。 TextViewにandroid:autoLink="web"を追加すればよいだけである。 これでテキスト内のURLをクリックしたらブラウザが開いてくれる。 めでたしめでたし。

といくなら楽でよかったのだが、この機能によるURLの処理はあまり正確ではない。

autoLinkの問題点

Spannable

URLが半角スペースで区切られていたり、2バイト文字以外で区切られていたりしたら正しくリンクとして拾ってもらえる。 例えばあいうえお https://android.gcreate.jp/ かきくけこというテキストであればURLの部分のみがURLとして識別される。 しかしあいうえおhttps://android.gcreate.jp/だとリンクを拾ってくれない。 またあいうえお https://android.gcreate.jp/がリンクになってほしいとした場合、「がリンクになってほしい」という部分までURLとして拾われてしまう。 2バイト文字でない半角カッコで囲ってかっこで(https://android.gcreate.jp/)とした場合、閉じカッコもURLに含まれてしまう。

URLの抽出がうまくいかない場合があるのが最大の問題であるが、URLのハンドリングをカスタマイズできないのもちょっと不便である。 例えばChromeカスタムタブでリンクを開きたい場合に、autoLinkでは対応できない。 autoLinkの場合、リンクをクリックするとACTION_VIEWの暗黙的インテントが発行される。

自前で処理する

以上の問題点を回避するには、自分でテキストにClickableSpanを設定してやると良い。

  1. テキストをSpannableStringに変換する
  2. テキストから正規表現を利用してURLを抽出する
  3. 抽出したURLを用いてSpannableStringにClickableSpanを設定する
  4. TextViewにSpannableStringをsetTextで設定する
  5. TextViewにsetMovementMethodを設定する
以上の手順で独自のClickableSpanを設定することができる。

コード的にはこんな感じ(Kotlinとandroid-ktxを利用している)。

val text = "あいうえおhttps://android.gcreate.jp/かきくけこ"
val spannable = text.toSpannable()
val matcher = Pattern.compile("(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]").matcher(text)
while (matcher.find()) {
  val url = matcher.group()
  val start = matcher.start()
  val end = matcher.end()
  spannable.setSpan(MyUrlSpan(url), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
}
textView.text = spannable
textView.movementMethod = LinkMovementMethod.getInstance()

MyUrlSpanは自分で定義する。 といっても大したことはやっていない。

class MyUrlSpan(val url: String) : ClickableSpan() {
    override fun onClick(view: View) {
        Snackbar.make(view, "$url clicked", Snackbar.LENGTH_SHORT).show()
    }
}

SnackbarでURLを表示しているだけ。 リンクをクリックした際の挙動はこのonClickでカスタマイズできる。

Read full post gblog_arrow_right

RoomでLiveDataを扱うときに注意

Architecture Componentで追加されたRoomを実際に使ってみた。

なんかややこしそうと思って敬遠していたのだけど、やってみると意外とそうでもないなという感じで「あれ、これだけ?」っていう感じで実装できた。実装できたと言っても、適当なサンプルだから簡単だっただけなんだけども。

https://github.com/gen0083/SampleArchitectureComponent

RoomではDatabaseDaoEntityの3種類を用意してやると、AnnotationProcessorによるコード生成が行われてSQLiteを簡単に利用できるというライブラリ。

LiveDataを返すようにDAOで定義することができ、この場合Databaseが更新されるとその変更がLiveDataを通じて受け取れる。いわゆるいいねボタン問題を解決する一つの解決策と言える。

LiveDataを返すようにDAOで定義を行うと、その処理はバックグラウンドで実行されるようなコードが生成される。逆にそれ以外の処理は自分で実行スレッドを考慮しないといけない。デフォルトだとRoomで生成されたコードをメインスレッドで実行すると例外が吐かれる。

LiveDataを返すようにする場合は注意しなければいけないことがある。

https://github.com/gen0083/SampleArchitectureComponent/blob/master/app/src/androidTest/java/jp/gcreate/sample/samplearchitecturecomponent/data/repository/TestRepositoryByRoomTest.kt#L66

このコードのようにsut.watch().valueとLiveDataのvalueを取得しても、その時点ではvalueはnullである。該当のテーブルのデータ全件取得と変更通知を兼ねてLiveDataを使おうとすると想定通りに動かない。実際にデータが取れるまでは時間がかかる。

LiveData<List<TestData>>としておけば、変更通知も受け取れる、データ全件取得もできる、便利じゃんと思ったけど、そうそううまくはいかなかった。といっても、RecyclerViewに表示する目的であれば、LiveDataを使うとすごい楽ちんであった。同じくSupport LibraryのListAdapterと組み合わせるとすごい楽。

ちなみにこのLiveDataを返すメソッドのユニットテストを行う場合、android.arch.core:core-testing:$architecture_version"を追加して、@get:Rule var executorRule = InstantTaskExecutorRule()とJunitTestRuleを適用してやるとうまいことテストができる。このルールを適用しないとバックグラウンドでの処理を待たずにテストメソッドが終了してしまい、LiveDataからデータを受け取ることなくテストが終了してしまう。

versionCodeの最大値

Google Playにアプリをアップロードする際、versionCodeを常に増やしていく必要がある。

Gradleを使ってこのversionCodeを自動的に生成する(versionNameと一緒に)ときに、versionCodeには最大値が存在するということは覚えておかないとならない。

https://developer.android.com/studio/publish/versioning.html

Google Playでは2.1Mが最大値となっているらしい。

versionCodeを生成するスクリプトを書く際は、APKを作成してそのAPKのversionCodeを確認するところまで気を配ろう。スクリプトでversionCodeが生成できていることだけ確認して、Google PlayにAPKをアップロードしたら最大値にひっかかって更新できなくなった、なんてことになったら悲しすぎる。

ちなみにこの問題にでくわしたのは、versionCodeをメジャーバージョン3桁、マイナーバージョン3桁、パッチバージョン3桁、ビルド番号3桁、MultiAPK識別用の符号1桁でversionCodeを生成したときである。

Google PlayにAPKをアップロードする前に気づけて良かった。

Droidkaigi2018アプリにコントリビュートする

去年に引き続き、今年もDroidkaigiアプリにコントリビュートすることができた。

去年は1行書き換える程度の細かいPR中心だったが、今年は機能の追加なんかもできて個人的に成長が感じられてうれしい。

Droidkaigiアプリは後から他の人が出したPRを見て「こういうやり方があるのか」と知見を得たり、知らないやり方を知れたりとそれだけで勉強になる。しかし私にとっては、リアルタイムで参加することこそ意義がある。

なぜなら、普段ソロで勉強・開発をしているので、コードに対するフィードバックが得られる機会がないからだ。PR送ればそんな貴重なフィードバックがいただける可能性があるのである。このチャンスを逃す手はない。万年ソロのせいでGit力すらないのだが、PRを送ることがGit力を鍛えるチャンスにもなってよい。

今回は、私のように独学でAndroid勉強しているというような人に向けて、この貴重なチャンスを最大限活用しようぜというスタンスで記事を書いてみようと思う。

準備

コントリビュートする前段階、準備に関しては公式情報が詳しいので参考にされたし。

DroidKaigi 2018 アプリをGitHubで公開しました

Issueを立てる

コードを書いてPRを送るのはハードルが高い、という人はまずはissueを立てることで貢献するのがいい。アプリを手元で実行してみて、ここの挙動がおかしくないだろうかなど、気づいたことを書けば良い。

日本語OKと言われても、みんな英語でやりとりしている中自分だけ日本語で書くのもなぁなんて思うかもしれないが、そこで躊躇するのはもったいないと思う。

例えばIssueを立てるのも勉強になるのである。こういう挙動がおかしいという問題提起に対して、誰かがそれを解決するPRを送ってくる。そうすると、この問題の原因はここにあって、こう対処すれば良かったのかと学びになる。

他の人が立てたIssueでも同じように学びになるが、やはり自分で立てたIssueの方が身につくと思う。

Issueを立てるときやPRの説明などに、動画をつけるのが親切でいいと思う。

私はAndroid Studioを使って端末の動画を撮影(Logcatのタブのところに動画撮影ボタンがある)、撮影したMP4のファイルをこのサイトを使ってアニメーションGIFに変換し、それをGithubに添付している。

Issueの原因を探る

すでに立っているIssueに対して、なぜそのような問題が起こっているのかの原因を探る。

今年は探る前に「このIssueに取り組みます」と手を挙げてassignしてもらうといい。やってみて無理そうならその旨を伝えればいいということなので、気軽に挑戦するといいと思う。

とは言え手を挙げてやっぱり無理でしたというのは恥ずかしいというのも真理。まずは立っているIssueの原因探求をやってみるところから始めてもいいかもしれない。

おそらくではあるが、すでに立っているIssueに対して追加情報を書き加えるのも立派なコントリビュートであると思う(コントリビューターには名前が載らないだろうが)。

原因がわかれば、対策を自分でできそうならassignしてもらえばよいし、原因は分かったが対処法が思いつかないというのであれば、他の人のPRを待って学びを得る機会が手に入ったと思えば良い。どちらにしろよい勉強になる。

PRを送る

ちなみに私はIssueを立てることなくいくつかPRを送っている。私の場合、Issueを一つ立てるのも時間がかかるので(主に英語力のせい)、コード書いてPR送ったほうが早い場合が多いからである。

Issueに取り組もうとして別の問題に気づいてそこに対処していたらPRの形になった、というパターンが多いだけであるが。

正直コードを書いているより、動作を確認したりIssueやPRの説明を英語で書くのに四苦八苦している時間のほうが長い。

コードに関しては、こんなコードで大丈夫だろうかとか、微妙なコードでレビュアーに負担を与えはしまいかとか、明確にだめとは言えないけど良くもなくて中途半端だなとか思われないだろうかとか、いろんな不安でいっぱいである。

しかしだからといって、せっかく書いたコードを送らないのは成長のチャンスを棒に振るうようなもの。エイヤとPR送るようにしている。先にも書いたとおり、私はずっとボッチ開発をしてきているので、誰かと一緒に作業していくという機会自体がめちゃめちゃ貴重なのである。

正直なところ、コードを書いてPR送ることこそ何よりも勉強になるので、特に私と同じような一人で開発しているとか勉強しているという人ほど、こういう機会を利用しない手はない。

PR送る上で気をつけていること

大したことではないのだけど、コミットする前にReformat codeを実行するようにするのは気をつけている。Macだとcmd + option + Lで実行されるやつ。

DroidkaigiアプリではcodeStyleが共有されているので、これを実行しておけばPRを送った後でスペースが足りないとかの機械的な指摘がぐっと減るので、PR画面が見やすくなると思う。

手作業でやると絶対忘れるので(去年の私はそうだった)、Android Studioでコミットする画面のBefore commitの部分のチェック項目にあるReformat Codeとかにチェックを入れておくのをおすすめする。

Read full post gblog_arrow_right

Adaptive Icon

Adaptive IconをFigmaを使って作ってみた。

Adaptive icon

Adaptive Iconは、開発者側はアプリのアイコンとなる前景画像と背景画像の2種類だけを用意して、後のアイコンの形はOS(デバイス側)にまかせてしまうという仕組み(という理解を私はしている)。

今まではランチャーアイコンとして四角いやつと丸いやつの2種類を別途用意していたけども、これからは2種類の画像(前景と背景)さえ用意しとけば後はOS側(正確にはホームアプリ?)がアイコンの形をよしなに表示してくれる。

といってもAPI26からの機能なので、現実的には依然として普通のランチャーアイコンは用意しなければならない。

とはいえ今回Figmaを使ってアイコンを作ってみたが、25以下のための画像データを用意するのはそこまで手間だとは感じなかった。108dpでアイコンデータを作っているので、その画像をAndroid Asset Studioに持っていってランチャーアイコンを生成するだけだったので。

Figmaを使ってアイコンデータ作成

今回はFigmaを使ってアイコンデータを作成した。ちなみにベクターデータである。

作成したforeground画像とbackground画像を、Adaptive Iconとして表示した場合にどうなるかは、このツールを使ってシュミレーションしながら確認した。

Adaptive Icon非対応の端末用の既存のランチャーアイコンは、Figmaから書き出した画像データをAndroid Asset Studioを使って生成した。

こんな感じで作成。

Icons

これは重ね合わせた状態だが、前景と背景別々に作ってある。

Fore back

どちらの画像も108dp四方になるように、108dpの矩形を別途用意した上で、その上にアイコン要素を描画するようにしている。そうしないとエクスポートした際に108dpの画像として出力できないからである。

作成した画像はforegroundとbackgroundそれぞれをSVGでエクスポートして、そのSVGファイルをVector Drawableに変換してAndroid Studioへ持っていった。

Vector Drawableへの変換はこのツールを利用した。

複雑な形状だとうまく変換できないこともあるので、適宜調整が必要だろう。Vector Drawableとしてうまく変換できたとしても、パスデータが複雑すぎるという警告が出ることもある。あまり複雑すぎるのも考えものである。

ちなみにFigma用のAdaptive Iconのテンプレートがあったので、こちら から利用させていただいた。

テンプレートのサイズはFigma上では432になっている。Adaptive Iconは108dpで作成するものだ。FigmaとかSVGの仕様とかに疎いのでよくわからないのだが、432のサイズでアイコンを作成してSVGでエクスポートしても問題ないのだろうか?

私はよくわからなかったので、アイコンサイズを108の大きさで作成した。

ちなみに432とか単位をつけずに書いているのは、Figma上での単位が分からなかったからである。432のままSVGエクスポートしてVector Drawableに変換すると432dpになってしまい、そこからXMLの数値を108dpに書き換えるだけで問題ないのかよくわからなかったので、最初からFigmaの段階で108のサイズで作成したというわけ。

ちなみにテンプレートは一旦フラット化してベクター情報に変換し、サイズを108に縮小してアイコン画像の位置調整のガイドとして利用させてもらった。

Adaptive Iconの対応状況

こうして作成したAdaptive Iconだが、これが表示できるかどうか使っているランチャー次第のようだ。

私の場合、実機Nexus6P(Android 8.1.0)に作ったAdaptive Iconのアプリをインストールしても、固定されたアイコンとしては表示されるものの、アニメーション(ぷるぷる動くような視覚効果)はしなかった。

Pixel Launcherだとぷるぷるするらしい。

私の使っているランチャーはGoogle Nowランチャーなので、Adaptive Iconにきちんと対応しているわけではないようだ。アイコンとして表示することはできるが、アイコンのシェイプを変えたり、アニメーションしたりしない。

試しに他のランチャーをインストールして試してみたところ、Adaptive Iconへの対応を謳っているランチャーであればAdaptive Iconがぷるぷるすることを確認した(私が試したのはNova Launcher)。

参考

3分で分かる?Android OのAdaptive Iconに対応しよう

Read full post gblog_arrow_right

Instant-appを試してみた

Instant-Appを試してみた。FlexibleTimerというアプリを作って、Instant-Appに対応させてみたのである。

https://play.google.com/store/apps/details?id=jp.gcreate.product.flexibletimer

Instant-Appというのは、アプリをインストールすることなく使えるようにする仕組みで、今回作ったアプリはhttps://app.gcreate.jp/flexibletimer/にアクセスすれば試すことができる。(Androidからアクセスすれば実行できるはず)

Instant-Appを実行できる環境

今のところAndroid6.0以上の端末であれば動くらしい。

Android6.0(API23)以上の端末であれば、おそらく設定 > Googleの中にInstant Appsという項目があると思う。それを有効にすればInstant-Appが実行できる。将来的にはAPI21以上もサポートされるらしいが、今は23以上が要件。

1つ注意点があって、Instant-Appはどうもどれか1つのアカウントでしか有効にできないみたい。私は2つのアカウントを1つの端末で使っていて、片方のアカウントでInstant-Appを有効にすると、もう片方は無効になってしまう。

このことで何を注意しなければならないかというと、Chromeにログインしているアカウントと、Instant-Appを有効にするアカウントは揃えておけということである。揃えていないとChromeからInstant-Appを提供しているURLにアクセスしても、Instant-Appが実行されないからである。揃えていなくても、例えばSlackで共有されたURLを開けばInstant-Appが実行されるし、Google Playからサイトに移動しても起動するので、Instant-App自体が使えないわけではない。

用意したURL(これはアプリを作成する際にAndroidManifest.xmlに定義する)にアクセスすれば、Instant-App用のAPKをGoogle Playからロードしてきて、インストールすることなくアプリが実行される。

URLは実際にアクセス可能でなければならない。つまり、Webサイトを所有していない場合には使えないということになる。所有していない場合はFirebase Hostingを利用してねというQAがある。(Stackoverflow

Instant-Appへの対応の仕方

今のところAndroid Studio3.0を使わなければInstant-App対応は不可能である。理由としては、3.0でなければInstant-Appを作成するために使うcom.android.featureプラグインが認識できないからだ。

まだ3.0は正式バージョンではないので、急いで対応しないとならないというものではないと思う。

既存のアプリをInstant-Appへ対応させるのは、そんなに難しい手順が必要なわけではない。たぶん、3.0でビルドできなくなったとか、gradleプラグインが対応してなくて動かないとか、そういった種類のトラブルに対応するほうがはるかに大変なだけだと思う。

細かいやり方はドキュメントを見てもらえばそんなに難しくはないと思う。

ちなみにcodelabで既存のアプリをInstant-App対応させる方法が一通り学べる。

はまったポイント

foreground serviceが動かない

Instant-AppではLong-running background serviceはサポートされていないが、foreground serviceは使うことができる。ことになっているが、現時点ではまだ対応されていないらしい。

Stackoverflow

Architecture Componentが動かない

私の場合LiveDataを使おうとしていたのだが、Instant-Appでは使えなかった。エラーは起きないが、LiveDataの変更が受け取れなかった。

https://issuetracker.google.com/issues/38493434

Read full post gblog_arrow_right

Architecture Componentを触ってみた

とりあえず軽く触ってみた。

codelab

Google IOの動画はとりあえず2つ見てみた。

最初のやつは「こんなん作ったでー」という話で、2つ目がその中身を説明っていう感じなので、時間がないなら2つ目だけ見ればいいんじゃないかなと思う。全部英語なので、私の英語力では雰囲気しかわからず時間対効果はあまり良くなかった気がしている。

3つ目もあるのだけど、これはまだ見れていない。タイトルから見るとRoomについての説明なのだろうか。

個人的にArchitecture Componentで気になっていたのは、いかにActivityのライフサイクルに振り回されなくてすむようにできるかという部分だ。つまり、LifecycleOwnerLiveDataViewModelについてである。とりあえずその観点で言うと、最初にあげたcodelabを触れば、雰囲気はわかった。2つ目の動画でだいたいスタンスがわかったような気がしている。

たぶん2つ目の動画で話していたと思うのだけど、このArchitecture Componentはすでに個々の開発者がそれぞれの工夫でActivityなどのライフサイクルによる呪縛を回避している手段を置き換えるためのものではないという話が、個人的にはしっくり来た。すでにライフサイクルとの付き合い方がうまくできている人は、別にそれでいいと。

ただ、Androidをこれから学ぼうとする人にとっては、Android特有のライフサイクルにまつわるあれこれは、学習していく上でつまづきやすいポイントで、さらにそれを回避するためのライブラリの使い方を学ぼうとすると余計にややこしくなってしまう。そこで、これからAndroidを学んでいく人にとって、とっつきやすいシンプルな仕組みを用意したよ、というのがArchitecture Componentということらしい。

私は最近ではDaggerやRxJavaを使って、Activityにはデータを持たせない、単に表示するだけのものとして扱うようにしてアプリを作るようにしている。そういった方法ですでにうまいこと回せているなら、無理して移行する必要はないのだろう。すでにうまいことやっている人にとっては、ちょっと触れば雰囲気がつかめるだろうから、とりあえずcodelabだけ触って雰囲気を掴んでおいて、1.0が出るのを待つくらいのスタンスでいいんじゃないかなと思う。

以下は触ってみた感想。

LifecycleOwnerとは

ざっくりした理解で言うと、ActivityとかFragmentとかServiceとか、Android特有のライフサイクルをもってるオブジェクトのことという認識でいる。

LiveDataの購読を行う際に引数に指定してやったり、ViewModelの生成・取得を行う際に引数に渡したりするのに出てくる。

LiveDataの購読に関しては、LifecycleOwner(Activityとか)のライフサイクルにあわせて自動的に購読解除してくれるらしい。つまり、いちいち自分でunsubscribe/disposeとかしたりしなくていいっていうこと。

またActivity等のライフサイクルにあわせた処理を行うのに利用したりできるっぽい。(codelabではLocationManagerから位置情報を受け取るクラスを作って、Activityのライフサイクルにあわせてセンサーの登録と解除を行うのに使っていた)

LiveData

UIに表示したりする実際のデータ。DataBindingでいうObservable<Hoge>みたいなものだし、RxJavaでいうObservable<Hoge>みたいなもの。

その実態は最新の値を保持してよしなに通知してくれるデータホルダー。RxJavaみたいなストリームではないよということだ。またスレッドの概念も持ってないので、バックグラウンドで処理してメインスレッドで通知みたいなことはしない。

LiveDataの更新はメインスレッドでないとできないみたいで、別スレッドからLiveData.setValue()したら落ちた。別スレッドから値を更新したい場合は、postValue()を使うらしい。

ActivityなどのViewは、このLiveDataを購読して、変更を受け取ったらUIを更新することだけ考えるような作りにするのが良いのだろう。

RxJavaでいうBehaviorSubjectみたいな動きをするなぁという印象を持った。

ViewModel

画面回転してもライフサイクルが継続してくれるもの。これが最初からあればAndroidアプリ開発はもっと楽になっていただろうと思う。

今までActivityにもたせていた状態やロジックを、全部こっちに持ってくればうまいことできると思う。

Activity.finish()を呼び出したらViewModelのライフサイクルも終了する(ViewModel::onCleard()が呼び出される)。

画面を回転させた場合はそのまま以前の状態を引き継いだものが、再生成されたActivityに渡ってくる。ただ、あくまでonConfigurationChangeで破棄されないだけなので、ホーム画面に一度移動した後にOSによって終了されたりした場合(Activityを保持しないが有効になってたりした場合)はViewModelのライフサイクルも終了してしまう。

データの永続化は別途考えないといけない。

軽く触ってみて

RxJavaなどの知識を持っているからか、割りとすんなり使えそうな気がしている。

それを抜きにしてもそんなにややこしくないと思うので、Androidをこれから学ぼうという人はとりあえずArchitecture Componentの使い方を学ぶと、シュッと入門できていいんじゃないだろうか。

触る前は「なんかいろいろあってややこしそうだな」とか思っていたのだが、触ってみると思いの外それぞれ独立していて、使いたいコンポーネントだけ利用すればいいという意味がよくわかった。

私個人としては、ViewModelはすぐにでも使いたい。Daggerを使って似たようなことをやっていたけれども、ViewModelを使うほうが楽だ。LiveDataはもうちょっと調べて、DataBindingとの組み合わせ方を掴んだら置き換えるかもしれない。

スワイプで削除できるRecyclerViewを実装するときの悩み

RecyclerViewを使うときに必ず実装するであろうRecyclerAdapter。List<Hoge>をRecyclerViewに表示するのに使う。

単にリストを表示するだけならあまり迷わないのだが、プラスアルファの処理を行う必要が出てきたときに私はよく悩む。例えば、現在進行形でもにょっているのが、リストのアイテムををスワイプしたらそのアイテムを削除したいというケース。とりあえず実装して動いてはいるのだが、削除に関するコントロール処理をどこに書くのが適切なのだろうかという疑問に対する明快な解を持ち合わせていない。

最近触っていないけど、FilteredHatebuというアプリでは削除に関する処理をPresenterに担わせた。Adapterは単にList<Hoge>とRecyclerViewの橋渡しをするだけというシンプルな作りだ。

一方で、現在作っているアプリではAdapterで削除に関する処理を行っている。この2つの違いがなぜ生まれたかというと、AdapterがList<Hoge>を持っているかどうかという問題に行き着く気がする。

RecyclerViewやListViewを使うとき、ネットで見かけるコードではAdapterにList<Hoge>を持たせるものをよく見かける。コンストラクタを使って渡すなり、セッターを使うなりして、AdapterにList<Hoge>をセットしてやる手法だ。単に表示するだけならこれで問題はないのだが、削除に関する処理を行おうとすると混乱し始める。

削除処理はList<Hoge>のアイテムを削除する処理を内包する。RecyclerViewの2番めのアイテムがスワイプされたら、List<Hoge>の2番めのHogeを削除しないといけない。ではその削除を実行するのは、AdapterなのかそれともActivityなのか、それとももっと他のもの(例えばPresenter)なのかがよくわからない。

List<Hoge>の操作が必要なのだから、List<Hoge>を管理しているものが削除すれば良い。となったときに、AdapterがList<Hoge>を持っていることが多いので、そのままAdapterに削除処理を実装することが多いのである。

削除可能なRecyclerViewの実装について、ベストプラクティスが知りたい。そして知りたいと思ったときに、ふと「そもそもAdapterにList<Hoge>を持たせるのはどうなんだろうか」と疑問に感じたのである。

私の理解では、AdapterはList<Hoge>とRecyclerViewの橋渡しをするもの、つまりHogeクラスを表示するためのViewに変換するのがその責務という認識だ。その認識からすると、AdapterにList<Hoge>をもたせて削除に関する処理が加わっている今作っているAdapterは、AdapterではなくてControllerになってる気がする。

そんなことを考えていると、そもそもRecyclerViewでアイテムをスワイプして削除させるのが間違っているのではないかという気分にもなってくる。別にAdapterにどれだけの責務をもたせるかは、開発者のさじ加減であって、個人の好きなようにしたらいいのかもしれない。

そんな堂々巡りのはて、まあ動けばいいかという結論に落ち着く。削除可能なRecyclerViewの実装、みんなはどうやっているのだろう。

Androidプログラミングを学ぶ上で大切だと思うこと

こっちのブログで書くか、別のブログで書くか迷ったのだが、プログラミングの話だしこっちに書こうかなと思う。

書こうと思ったきっかけは、Androidプログラミングを教える仕事を受けたこと。まあ個人的に、私などが人様に何かを教えるなどおこがましいという思いはあったものの、まあ何事もやってみなければわからないということでやってみた。

Android特有の、ライフサイクル周りの話だとかは、まあ知っていないと辛いよねっていうことではあるんだけど、それよりもっと基礎的な、プログラミングを学ぶ姿勢とでも言おうか、今回書きたいのはそこについてだ。Androidプログラミングとタイトルにしているのは、私がAndroidしか知らないから限定しているだけで、たぶん他の言語でも同じなんじゃないかなと思う。

私はずっと一人でAndroidのプログラミングをやってきた。ほぼ独学である。書籍とネットの情報を頼りに黙々とやってきた1。そんなやつが人様にプログラミングを教えるのもおこがましい気がするものの、3ヶ月間教えてみて大事だなと思ったことが3つある。

1つ目は言ってしまえばコミュニケーション能力である。いきなりプログラミング関係ないじゃないかと思われるかもしれないが、これはとても大事である。別に話を弾ませたり、相手の気持ちを慮ったりする能力が必要だというわけではなく2、自分の考えを相手に伝えようと努力できることって、プログラミング関係なしに大事だなと思ったのである。具体的なシチュエーションとしてはエラーを伝える場面でよくそう思った。なんだかよくわからないけれどもこういうエラーが出たんですと、わからないなりに説明しようとする人とそうでない人がいる。エラーの確認の仕方は知っているかどうかの問題なので、初心者だから見込みがないとかそういう話ではない。しかし、わからないなりに説明をしようとする人に、私は伸び代を感じたのである。

2つ目はチャレンジ精神である。とりあえずやってみることが大事だということ。こう書いたら動いた、だったらこう書けば違う動きになるのではないかと仮説を立てて、挑戦してみる姿勢が大事だ。知らなければどうしようもない部分もあるので、教えてもらうことも大事ではあるが、教えてもらうのを待つだけではなく、自ら挑戦する姿勢もまた大事なのであると言いたい。

3つ目は自分で調べる力である。Androidは特にソースコードがすべて公開されているので、なぜそう動くのかはコードを読めば分かる3。ソースコードを読むのでなくても、公式のドキュメントを読んで調べることなら誰でもできるはず4。Androidのプログラムを書いていると、便利なライブラリのお世話になることがとても多いが、そういったライブラリの使い方を、ライブラリのドキュメントやソースコードから調べることは重要だ。

以上3つ。プログラミングを教えているのに、プログラミングの知識、変数がどうのとかJavaがどうのとかとかよりも、もっと基礎的なことの方が大事に思えたことが意外に感じられた。

最後に、これからプログラミングを勉強しようと思っている人は、できるだけ効率的に学びたいなと思っていることだろう。きっと、いい師匠(先生)を見つけるのが効率的なんだろうけれど、どうやって探すのかは私が知りたい。だからそんな効率的に学ぶ方法を探す前に、さっさと何かプログラミングしてみる方が早いと私は思っている。

Androidプログラミングで言えば、とりあえず市販の本でもネットの情報でもなんでいいので、とりあえず書いてあるとおりにやってみるのがよいと思う。書いてある通りにやってうまくいかなければ、誰かに聞くのがいいだろう。

実は、そういう「書いてあるとおりにやってみたけど動かない」という状況は、スキルアップにちょうどいいシチュエーション5なのだけど、初心者はただ辛いだけなので、誰かに聞くのが手っ取り早い。Twitterでつぶやいてみるとか、teratailとかStackoverflowとかで質問してみるとか6。学校の先生がいるなら、先生に質問するのがいいだろう。聞ける人がいるということはとても恵まれたことなので7、そんな環境にいる人は今のうちに有効活用するべきだと思う。

大事だと思った3つのこと。コミュニケーション能力、チャレンジ精神、自分で調べる力。何にでも言えることじゃないかと思うけど、何にでも言えるからこそ大事なんじゃなかろうか。実際、教えていてこの人はプログラムに向いている・向いていないと感じることが何回かあった。それはなぜそう思うのかと考えてみたら、プログラミングの知識よりも本人の姿勢によるところが大きいと思ったのだ。


  1. ネットで公開される知見に頼るところが大きいのでとてもありがたく拝見している 
  2. あるにこしたことはないが 
  3. 理解できるかはともかく 
  4. 理解できるかはともかく 
  5. 原因を調べて解決することは、プログラミングをしていく上で何度も通る道であり、そういうハマりを経て人は成長していくのである 
  6. 質問する敷居の高さは、teratailの方が低いとは思うが、思い出してほしい。コミュニケーション能力が大切だということを。インターネット上の人々はあなたの状況を理解しているエスパーではないので、聞き方が悪いと答えは返ってこないどころか、逆に傷つくことになるかもしれない。 
  7. 学生という立場は学生という立場を失ってはじめてそのありがたみが分かるのだ 

Firebase Crashを使ってみた

Firebase Crash Reportingを使ってみた。今まではCrashlyticsを使っていたのだが、最近はFirebaseをアプリに組み込むことが多いので、クラッシュレポートもFirebaseでやってみようかなというのがことの始まり。

導入手順的に考えると、Firebase Crash Reportingはとても簡単。Firebaseを使うプロジェクトであれば、dependenciesに'com.google.firebase:firebase-crash:<VERSION>'を追加するだけで終わり。これだけでアプリがクラッシュしたら勝手にレポートをあげてくれる。

Firebaseを使う設定に関しても、Android Studioに組み込まれているFirebaseのツール(?)を使えばいとも簡単に使えるようになるので、導入の敷居はCrashlyticsに比べるととても楽である。

一方で、Crashlyticsと比較すると面倒くさいポイントもいくつかあって、単純に乗り換えればいいやという話でもなさそうなのが悩ましい。

mapping.txtのアップロード

ProGuardをかける場合に難読化されたスタックトレースを解読するため、mapping.txtのアップロードが必要になる。Crashlyticsの場合、設定が必要だが自動的にアップロードを行ってくれる。

一方でFirebase Crash Reportingは自分でFirebase Consoleにアップロードしなければならない。gradleタスクでアップロードするための方法が用意されてはいるが、Crashlyticsと比較すると「自動アップロード」とはいえない。初期導入が簡単な反面、ProGuardのmapping.txtをアップロードする設定を行う手間がある。

https://firebase.google.com/docs/crash/android

  1. ルートのbuild.gradleのclasspassに`'com.google.firebase:firebase-plugins:1.0.5'`を追加
  2. app/build.gradleに`apply plugin: 'com.google.firebase.firebase-crash'`を追加
  3. Firebase Consoleからプロジェクトの設定→サービスアカウント→クラッシュレポートから、新しい秘密鍵の生成を行いダウンロードする(jsonファイル)
  4. ダウンロードしたファイルへのパスを`FirebaseServiceAccountFilePath`というプロパティに記述する1
  5. `./gradlew :app:firebaseUploadReleaseProguardMapping`を実行してアップロード(buildVariantなどによってタスク名は変わる)
`firebaseUploadXXX`というタスクを実行しないといけないので、そのままだと確実に忘れそう。`assembleRelease`を実行したらこのタスクも実行するように指定できたらなぁと思ったのだけど、やり方がわからなかった。

そして依存させるなら、assembleReleaseよりもapkをGoogle Playにアップロードするタスク(自動化しているなら)に依存させるのが良さそうではある。

debugビルドでアップロードしてほしくない問題

Firebase Crashは特に何もしなくとも、アプリがクラッシュすればスタックトレースをアップロードしてくれる。カスタムApplicationクラスに初期化処理を書いて・・・なんてことすら必要ない。ContentProviderの初期化の仕組みを使ってライブラリ側で勝手に初期化しているとかなんとか見た気がする。ある意味便利ではあるが、一方で不便なところもある。それは、クラッシュレポートを送信させない手段が存在していないところである(たぶんない)。

CrashlyticsはカスタムApplicationで初期化をする必要があり、ここで例えばデバッグビルド中は送信しないようにしたり設定できる。Firebase Crashにはそういうのはないっぽい。そもそも自分で初期化しないし、送信を停止するようなメソッドも見当たらない。

これはFirebase CrashをreleaseCompileで組み込めば一応回避は可能である。

一方で、プライバシーポリシーの問題というか、ユーザの許可を得ずにクラッシュ情報を収集してよいのかという問題があると思う。このあたりの法的問題に、他の開発者さんはどう対処しているのか私は知らないが、個人を特定する情報は含まれていないとしても、例えばユーザにクラッシュレポートを送信しないような選択肢を提供したいときに、Firebase Crashではそれができないということになる。クラッシュレポートについてオプトアウトできるようにしてあるアプリがあるのかと言われるとよくわからないけれども。まあもし対処する必要が出てきたとしたら、きっとしれっと無効にできるようにアップデートされるのかもしれない。

logとreport

Firebase Crashは基本的には組み込めばそれで終わりな感じで、後は任意のタイミングでFirebaseCrash.log()とかFirebaseCrash.report()などを使ってクラッシュ時の情報を付け加えるくらいしかやることはない。

log()はクラッシュレポートにイベントとして情報を追加することができるものである。

report()は例えばtry~catchでcatchした例外のスタックトレースを送信するのに使う。

私はどちらもうまいこと使いこなせる自信がない。今までもクラッシュレポート見ても、一体どういう状況で発生しているのかよく分からなくて対応ができなかったことがよくある。log()を使えば原因を特定するのに有効な情報を付け足せるのだろうが、どういう情報を付け足せば原因把握に役立つのかはいまいち分からない。


  1. gradle.propertiesなどで指定すれば良い。プロジェクトルートに秘密鍵のファイルを配置したのであれば、`FirebaseServiceAccountFilePath=../<秘密鍵のファイル名>`という感じ。