FlutterでTwitterクライアントを作ってみた

FlutterでTwitterクライアントを作ってみた。 レイバン製造機になる未来しか見えないので公開したりはしないけれど。 とりあえずマルチアカウント対応とタイムラインの取得を実装して力尽きた。 twitter_loginなどのライブラリがflutter(dart)でも存在しているようだが、そういうのは利用せずに直接APIとやり取りする形で実装した。

使ったライブラリ

Access Tokenの取得はこの記事を参考にして実行した。

Kotlinで書きたい問題

Androidネイティブから入門した身からすると、dartではなくKotlinで書きたいという気持ちでいっぱい。

はじめはセミコロンのつけ忘れが多発し、その次はインスタンスを生成する際にnewを付け忘れるが多発して困ったからという理由が大きかった。newのつけ忘れは、なくても動く場合と、つけないと動かない場合があって、その違いがいまいち分かっていない。 しかも付け忘れて動かないのは、実行しないとわからないのが更に困りものだった。

Kotlinで書きたい理由は、今だとこの2種類の理由から。

  • data classが使いたい
  • Null許容・非許容を型で表現したい

data classについてはdartのissueに上がっているので、そのうち実装されるかもしれない。 Javaで書くことに比べたらdartでのデータオブジェクトの記述はスッキリしている(getter/setterを定義する必要はない)のだが、data classならequalsメソッドをいちいちオーバーライドしなくても済むとか、そういうところが便利だと思うので、早くdartにもdata class来てほしい。

まあそれは来たらいいな程度の気持ちだけど、null許容・非許容を型で表現したいのは結構切実な問題である。 dartはカジュアルにnullが飛んできすぎな気がする。 dartではあらゆるものがオブジェクトなので、intだろうと初期化されていなければnullとなるので、nullと格闘することが多かった。 このあたりは私の実装の仕方が悪いという面も大きいかもしれない。 が、それにしてもnull許容・非許容が型で表現できるKotlinを知っていると、nullで消耗するのがつらい。 Textにnullが渡ってアプリがクラッシュする→どこでnullが渡ったのか把握しきれないとかいうのが多くてね・・・。

dartのasync/await便利

asyncなメソッドを定義してやればそれだけで非同期処理が記述できてしまうのは非常に便利だと思った。 awaitと組み合わせて複数のリクエストを同期的に書けるのはめちゃくちゃ楽だった。 Androidネイティブだと、RxJavaを使って連結させてやるような処理がシンプルに書けるのはよい。 ネットワークを使った処理がシンプルに書けるのはすごい楽だった。

redux

アプリ内の状態を管理するのにreduxを使った。

最初はflutter_reduxから導入の仕方を学んだのだが、redux初見の私にとってはこれは悪手だった。 なぜシングルトンで管理しているはずの状態を、わざわざ_ViewModelに移し替えているのか最初は理解できなくて混乱した。 flutter_reduxはredux.dartとflutterの橋渡しをするライブラリなので、そこを切り離して考えないといけないだろう。

dart.redux

dart.redux

Read full post gblog_arrow_right

Android Studio2.x時代のプロジェクトを3.0環境へ更新

ストアに公開しているアプリは2018年8月までにtargetSdkVersionを26以上にする必要がある。その関係で以前にリリースしたアプリを更新しているのだが、その際に最初のハードルとなるのが古いAndroid Studioで作ったプロジェクトを現在のバージョンに更新する作業である。

最近何回も同じことをやっているので、備忘録的に書いておく。

基本的には全部Migrate to Android Plugin for Gradle 3.0.0に書いてあるので、そのとおりにやるだけである。

書いている途中でAndroid Studio 3.1が正式版になった。が、3.0から3.1への更新はそんなに大きく変更しなくても済むので、基本は3.0へのマイグレーションガイドに書いてあることに対応すればよいはず。

Gradleのバージョン更新

まず始めにGradle(Gradle wrapper)のバージョン更新を行う。Android Studio3.0は最低でもGradle4.1以上が必要。(3.1はGradle4.4以上)

gradle wrapperの更新はgradle-wrapper.propertiesファイルを書き換えてgradle syncを行うだけ。

私は./gradlew wrapper --gradle-version=<Gradleのバージョン>コマンドで更新している。これで更新したらシェルスクリプトの更新もかかる場合があるので、こっちのほうがいいのかなという雰囲気で選んでいるだけだったりする。

コマンドで更新した場合、distributionUrlで指定されるgradle wrapperが4.x-bin.zipといった感じでバイナリのみのものになる。Android Studioはallにしとけと変更を促してきて、結局手書きでdistributionUrlを書き換えることになるので、普通にマイグレーションドキュメントにあるようにgradle-wrapper.propertiesを書き換えるだけでいいと思う。

ちなみに利用するgradleのバージョンだが、私の場合Twitterで見かけたGradleのリリースツイートを見て覚えてるバージョンを利用している。今だと4.6。

使えるバージョンはhttps://gradle.org/releases/で確認できる。基本的には最新使っとけばいいんじゃないだろうか。

build.gradleの更新

  • android gradle pluginのバージョンを更新
  • repositoryに`google()`を追加する(先にGradle wrapperのバージョンを上げておく必要がある)
  • 利用しているgradle pluginのバージョン更新

プロジェクトで利用しているgradle pluginのバージョンが古い場合、原因がよくわからないエラーが多発する。例えばNo signature of method: com.android.build.gradle.internal.scope.VariantScopeImpl.getGenerateRClassTask() is applicable for argument types: () values: []とか。こういうのは利用しているgradle pluginを最新のバージョンに更新すると解消されることが多かった。

基本的にAndroid Studioが利用しているプラグインの新しいバージョンがあれば教えてくれるはず(網掛けになって新しいバージョンがあることを示唆してくれる)ので、それに従ってgradle pluginのバージョンを上げてみると解決する場合が多いだろう。

エラーメッセージで検索してもこれといった解決策が見つからないという場合には、利用しているプラグインのバージョンを上げることを試してみよう。

ライブラリプロジェクトを利用している場合

昔はライブラリプロジェクトを利用している場合、publishNonDefault trueを設定して、アプリケーションモジュールをdebugビルドするときは、ライブラリプロジェクトもdebugビルドにするなんて指定をしている人もいただろう。

この設定をしている場合、Android Studio3.xにアップデートするにあたってちょっとした手直しが必要になる。

Android Studio3.0からはライブラリモジュールのビルド設定は、利用するアプリケーションモジュールのものと同じものが利用されるようになった。appモジュールでdebugビルドを選んだら、自動的に依存しているライブラリモジュールもdebugビルドでビルドされる。

そのため以下の手直しが必要。

Read full post gblog_arrow_right

flutterやるならぜひ登録しておきたいLive Template

Flutterを触り始めたのだが、とにかくWidgetのレイアウト変更がツライと感じていた。

AndroidでいうところのViewのレイアウトがコードでゴリゴリ書いていく感じになっているので、インデントが深くなってツライ。Widgetの考え方もAndroidのViewとはちょっと違う(レイアウトに関する設定はレイアウト情報を持つWidgetでくるむとか)

ゼロから作る分にはまだいいのだが、一度作ったレイアウトに対して、「このTextにmargin付け加えたい」となったときがツライ。シンプルなレイアウトならまだいいが、Widgetが何個も登場したり、何回層もネストされてたりすると正直触りたくない。

例えばこんな感じ。

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text("FriendlyChat"),
      ),
      body: new Column(
        children: <Widget>[
          new Flexible(
              child: new ListView.builder(
                padding: const EdgeInsets.all(8.0),
                reverse: true,
                itemBuilder: (_, index) => _messages[index],
                itemCount: _messages.length,
              ),
          ),
          new Divider(height: 1.0,),
          new Container(
            decoration: new BoxDecoration(
              color: Theme.of(context).cardColor,
            ),
            child: new Text("hoge"),
          ),
        ],
      ),
    );
  }

これはまだかわいいものだが、それでもめんどうくさい。

そこでLive Templateを追加して、IntelliJのSurrond With機能を使って簡単にWidgetを囲えるようにしてみた。Flutterのチュートリアルを試したりするのに活躍するのでかなり捗ると思う。

Live Templateを定義

Preference > Editor > Live Templateが定義場所。

定義する場所は別にどこでもいいのだろうけれど、Flutterに関することなのでFlutterのところに追加した。

new Column(
  children: <Widget>[
    $SELECTION$
  ]
),

Dartファイルに適用されるように指定して、Reformat according to styleにチェックも入れておく。

Sample

Read full post gblog_arrow_right

カスタムフォトウォッチVer2の紹介

Android搭載のスマートウォッチ向け、自分の好きな画像を文字盤に設定することのできるアプリがカスタムフォトウォッチです。

Google Play で手に入れよう

アプリ実行に必要なもの

  • Android搭載のスマートウォッチ
  • スマートウォッチとペアリングしたAndroidのスマホ(今のところiPhone対応の予定はありません)

スマートウォッチのウォッチフェイスをカスタマイズするアプリなので、スマートウォッチがないと意味をなしません。スマホだけでは単なる画像編集アプリのような何かにしかならないのでご注意を。

Android搭載スマートウォッチはiPhoneともペアリングして使えますが、今のところこのアプリはiOSには対応していません。

カスタムフォトウォッチはスマホとスマートウォッチの両方にインストールする必要があります。

以前のバージョンであればスマホからインストールすれば、ペアリングしているスマートウォッチにもアプリが自動的にインストールされていましたが、Ver2からはそれぞれ個別にインストールが必要になっていますのでご注意ください。

実行に必要なソフトウェア

  • GooglePlay開発者サービス
  • Wear OS by Google(スマホとスマートウォッチのペアリングを行うアプリ)

これらはスマートウォッチとスマホとの間の通信処理を行うために必要となっています。インストールされていてもバージョンが古い場合にアプリがうまく動作しないおそれがあります。バージョンが古い場合はアプリから更新を促すメッセージが表示されると思うので、各アプリの更新をお願いします。

メッセージを無視しても使うことはできると思いますが、スマホから設定した画像がスマートウォッチに表示されないなど、データの同期がうまくいかないおそれがありますのでご注意ください。

使い方

スマホ側

まずは画像を追加しましょう。右下のプラスボタンを押せば画像を追加できます。

Add new image

スマホ側からスマートウォッチに設定したい画像を取り込み、文字盤に表示する時刻や日付などの情報を設定します。スマホに保存されている画像データや、カメラからその場で撮影した画像などを取り込むことができます。

Import image

中央に表示されている枠がスマートウォッチの文字盤に設定される範囲になります。拡大・縮小などで位置を調整してみてください。拡大・縮小の他回転(90度単位ですが)や反転もサポートしています。位置が決まったら切り取りをおこなってください。

Cut image

このアプリでは時刻の表示位置などを細かくカスタマイズできるので、画像に合わせて最適な位置に調整していただけます。

Customize

以上の手順で画像データを用意すれば自動的にスマートウォッチに同期されます。

スマートウォッチ側

まずはウォッチフェイスにカスタムフォトウォッチを設定してください。スマートウォッチの文字盤で左右にスワイプするとウォッチフェイスの変更ができると思います。そこからカスタムフォトウォッチを選択してください。

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をアップロードする前に気づけて良かった。

Androidアプリ設計パターン入門を読んだ

Androidアプリ設計パターン入門を読んだ。製本版を買ったとばかり思っていたが、電子版で出資していた。思い込みとは恐ろしいものである。

せっかく出資して購入したのだから、もっと早く読んで(アーリーアクセス版で)疑問点なり質問すればよかったなと今更後悔している。

購入した動機は、設計に関する考え方を知りたかったからだ。

考え方というと勘違いされてしまうかもしれないが、○○という設計パターンはこういうところで優れている、なんてことを知りたかったわけではない。他の人(というか日本Android界を引っ張っていくようなエンジニアの方々)が、どういうことに困ってどういう思いで設計パターンを選んでいるのだろう、というところが知りたかったのである。

その観点から言うと、やっぱりみんな困るところは一緒なんだなぁということが分かってよかった。個人的には特に4章が興味深かった。

そもそも私自身はこれまでずっとソロで開発してきているので、チーム開発の話自体が新鮮である。みんなこういう感じで開発してるのかと、そういう雰囲気が感じられる事自体が何よりありがたい。そういう意味で買って(出資して)良かったなと思っている。

正直な話、ソロ開発する上ではどの設計パターンで作るかについて神経質になる必要はないと思う。最近の私は、とりあえずActivityのライフサイクルに振り回されないように作ればいいかな程度のゆるい考え方で、設計パターンの遵守より作りやすさを優先して作っている。

本書を読んでいて思ったのは、やはり設計パターンはチーム開発において重要になってくるものだということだろう。

どの章も、チームでの開発の視点で描かれている。設計パターンの導入とチームでの開発は切っても切り離せない問題なのだ。

本書は、Androidにおいてこの設計パターンで作れば間違いがない、このパターンこそ最強であるからこれをやっておけ、という内容にはなっていない。だから、結局どのパターンを学んでおけば安泰なのか知りたい、という観点で読むと肩透かしを食らうと思う。

そもそもそんな銀の弾丸があるなら、みんながそのパターンで作っているはずだ。

私は本書を読んでいて、Fluxパターンはなんか良さそうだなと思ったのだけど(データの流れを一方通行にするというところと、状態を保持するStoreが独立してるってところが良さそうに感じた)、じゃあこのパターンでリストのスクロール位置を保存しようと思ったらちょっと大仰すぎやしないかと感じる。スクロールの度にActionを発行して、Storeの状態を更新していくのだろうか。onSavedInstanceSateで位置を保存するだけでいいんちゃうのと思ったりする。

そんなわけで銀の弾丸はない。どの設計パターンを学べばいいですかっていう人にはもしかしたら合わないかもしれない。

Android開発においては、どんな設計パターンがどういう問題を解決するために使えるのか、どういう作り方をするのかという考え方を学んでおくしかない。自分が他のチームにジョインした時に困らないように。

これからチーム開発を行っていけるようにするためには、どういう問題を解決するためにどんな設計パターンが存在していて、どんな感じにAndroidに適用してくのかの考え方を知っておくことが大事だと思う。その考え方について、本書はMVP、MVVM、Fluxといった設計パターンを、著者の考え方とあわせて書かれているので、独学で勉強していますとかチーム開発の経験がない人ほど有用であるように思う。少なくとも、ずっと独学で勉強してきた私にとってはとても有用な内容だった。

具体的にどうやって実装していくかなんて言うのは自分で手を動かしてみないとわからない。本書で大事なのは著者の頭の中身が覗ける(考え方が赤裸々に書かれている)ということである。

ああ、私も誰かと一緒にアプリ開発したい(切実)。

Android アプリ設計パターン入門

Android アプリ設計パターン入門

  • 著者:日高 正博,小西裕介,藤原聖,吉岡 毅,今井 智章,
  • 製本版,電子版
  • PEAKSで購入する

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

com.google.android.gms.tasks.Task<T>のユニットテスト

com.google.android.gms.tasks.Task<T>にまつわり処理でユニットテストを書きたいということがある。例えばFirebaseを使うときにそんな思いに駆られることがあるだろう。

私はFirebase Realtime Databaseの処理をラップするクラスを作成したときに、ユニットテストを書きたいと思った。

何らかの処理を要求した後、返ってくるのがこのTask<T>というクラスである。リクエストはすべて非同期で処理されるので、処理の結果はaddOnSuccessListenerなどを使ってリスナーをセットして、そのリスナーの中でリクエストの結果を受け取るようなコードになる。

ユニットテスト書くときに困るポイントは2つ。

  • どうやって`Task`を生成するか
  • 処理結果をリスナーで受け取るにはどうするか

Taskの生成

Task<T>を生成するにはTasks.forResult()などのメソッドを利用するとよい。

com.google.android.gms.tasks.Tasksがファクトリメソッドみたいなものなので、こいつ経由で目当てのTask<T>が作れる。こうすることで起点部分の問題はクリアできる。

https://developers.google.com/android/reference/com/google/android/gms/tasks/Tasks

リスナーの注意点

処理結果をリスナーで受け取る際は注意が必要である。

利用する際にはaddOnSuccessListener()に渡す引数は1つ、この場合は成功時に行う処理だけ指定していると思う。

この場合、OnSuccessListeneronSuccessメソッドがメインスレッドで呼び出されることに注意が必要である。

実はTask<T>に対して設定するOnSuccessListenerOnFailureListenerOnCompliteListenerはそれぞれ、特に指定を行わない限り必ずAndroidのメインスレッド上で呼び出される。このメインスレッドで呼び出されるというのは、Looper.getMainLopper()で取得されるメインスレッドである。

つまり、JVMユニットテスト(testディレクトリに配置するユニットテスト)ではリスナーの処理が呼び出されないことに注意が必要である。

この問題に対応するためには、Instrumentation testとして実行する(androidTestディレクトリに配置する)か、addOnSuccessListenerでリスナーを設定する際に、スレッドを指定(Executorを指定)してやればよい。

val executor: Executor = Executors.newSingleThreadExecutor()
Tasks.forResult("test")
  .addOnSuccessListener(executor, OnSuccessListener { println(it) })

ちなみにそのままでは処理タイミングの問題で、リスナーの処理が呼び出される前にテストメソッドが終了してしまう場合がある。そのため、CountDownLatchなどを利用するなどして、リスナーの処理が呼び出されるのを待ち合わせたりする必要がある。

待ち合わせが必要になるのは、Instrumentation Testでも同じである。たとえTasks.forResult()を使って即座に処理が完了するTask<T>にリスナーを設定しようとも、タイミングによって呼ばれないことが起こりうるので、リスナーの呼び出しのテストを行う場合には必ず待ち合わせの処理を挟むことが必要。

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