ひだまりソケットは壊れない

ソフトウェア開発に関する話を書きます。 最近は主に Android アプリ、Windows アプリ (UWP アプリ)、Java 関係です。

まじめなことを書くつもりでやっています。 適当なことは 「一角獣は夜に啼く」 に書いています。

Android アプリの Drawable リソースのエイリアスの作成について

当たり前の内容だけど日本語ドキュメントが間違っててバグを埋め込んでしまった (リリースはしてない) ので共有。

Drawable リソースのエイリアス作成

res/values/drawables.xml ファイルみたいなファイルを作って、そこに <drawable name="alias_name">@drawable/target_drawable</drawable> みたいなタグを書けばエイリアスを作れる。

To create an alias to an existing drawable, use the element. For example:

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <drawable name="icon">@drawable/icon_ca</drawable>
</resources>

If you save this file as drawables.xml (in an alternative resource directory, such as res/values-en-rCA/), it is compiled into a resource that you can reference as R.drawable.icon, but is actually an alias for the R.drawable.icon_ca resource (which is saved in res/drawable/).

App resources overview  |  Android Developers

qualifiers との兼ね合い

以下のように、qualifiers 付きディレクトリにある実体のある Drawable リソースとエイリアスとで、ちゃんと切り替わることを確認。

  • res/drawable-v23/foo.xml というリソースファイル
  • res/values/drawables.xml<drawable name="foo">@drawable/target_drawable</drawable>

日本語ドキュメントが間違ってる件

日本語ドキュメントだと、

既存のドローアブルのエイリアスを作成するには、<bitmap> 要素を使用します。次に例を示します。

って書かれてる。 が、実際にこれをやって (Bitmap Drawable 以外の?) Drawable リソースのエイリアスを作成しようとすると実行時にエラーが発生する。 (API level 16、23、25 のエミュレータで確認。)

ドキュメントの誤りについては https://issuetracker.google.com/issues/78862550 に報告した。

`bitmap` 要素を使ったときのエラー例
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
    android:src="@drawable/simple_drawable" />

上のような XML ファイルを用意して、Drawable として読み込もうとすると以下のようなエラー。

    java.lang.RuntimeException: Unable to start activity ComponentInfo{info.vividcode.sample.simple/info.vividcode.sample.simple.MainActivity}: android.view.InflateException: Binary XML file line #31: Binary XML file line #31: Error inflating class ImageView
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2416)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2476)
        (中略)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
     Caused by: android.view.InflateException: Binary XML file line #31: Binary XML file line #31: Error inflating class ImageView
        at android.view.LayoutInflater.inflate(LayoutInflater.java:539)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:423)
        (中略)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616) 
     Caused by: android.view.InflateException: Binary XML file line #31: Error inflating class ImageView
        at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:782)
        at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:704)
        (中略)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616) 
     Caused by: android.content.res.Resources$NotFoundException: File res/drawable/simple_drawable_3.xml from drawable resource ID #0x7f02005f
        at android.content.res.Resources.loadDrawableForCookie(Resources.java:2640)
        at android.content.res.Resources.loadDrawable(Resources.java:2540)
        at android.content.res.TypedArray.getDrawable(TypedArray.java:870)
        at android.widget.ImageView.<init>(ImageView.java:152)
        (中略)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616) 
     Caused by: org.xmlpull.v1.XmlPullParserException: Binary XML file line #2: <bitmap> requires a valid 'src' attribute
        (以下略)

Android における View にまつわる状態とライフサイクルについての考慮のメモ

Activity が再生成される際の、View の onSaveInstanceState メソッドと onRestoreInstanceState メソッドについて個人用にまとめておく。

Activity の再生成

  • Activity が再生成 (re-creation) される際には、以前の状態が Activity#onCreateActivity#onRestoreInstanceState に渡される。
  • Activity#onRestoreInstanceState が呼ばれるのは、Activity#onStart が呼ばれた後。
    • 『This method is called after onStart() when the activity is being re-initialized from a previously saved state, given here in savedInstanceState.』 (Activity#onRestoreInstanceState より)
  • Activity#onRestoreInstanceState が呼ばれるのは、Activity 再生成後の最初の Activity#onStart が呼ばれた後の 1 回だけ。 (Android 8.0 の端末で実際の挙動を確認。)

View 周り

  • View にも View#onRestoreInstanceState メソッドView#onSaveInstanceState メソッドがあるが、これらはどこから呼ばれるのか?
  • Activity#onRestoreInstanceState メソッドや Activity#onSaveInstanceState メソッドのデフォルト実装による。
    • 『The default implementation of this method performs a restore of any view state that had previously been frozen by onSaveInstanceState(Bundle).』
  • Window あたりがルートビューの View#saveHierarchyStateView#restoreHierarchyState などを呼んで、そっから View#dispatchSaveInstanceStateView#dispatchRestoreInstanceState やらが呼ばれて、最後に View#onSaveInstanceStateView#onRestoreInsntaceState やらにたどり着く。
    • android:saveEnabled の考慮は View#dispatchSaveInstanceState で行われる。
    • 子 View の View#dispatchSaveInstanceState を呼ぶのは ViewGroup#dispatchSaveInstanceState が行っている。
    • ここら辺は実際のコードをみて確認した。 API Level 27。

View にまつわる状態とライフサイクルについての考慮 (?)

  • View Model やら Presenter みたいな、いわゆる Presentation Model 的なものを作っていると、Activity の再生成時の View の状態 (View#onRestoreInstanceState で復元されるやつ) と、Presentation Model 側の状態の同期をどうとるかで悩むことになりがち。
  • 道はいくつもある
    • カスタム View を作って、その内部に状態を持たせる。 状態の保存はカスタム View の View#onSaveInstanceState に任せる。
    • View の状態管理は完全にアプリケーション側の責務ということにして、Android フレームワークの力には頼らない。 View には android:saveEnabled="false" を設定する。
    • Activity 再生成時の状態復元は View だけでやって、Presentation Model 側では View 側の状態復元をフックにして状態を変える。
      • 具体的に言うと、例えば EditText の入力内容に対応して Presentation Model 側で状態を保持する設計を考える。 Activity 再生成時の初期状態では EditText も Presentation Model も入力内容がない場合の状態になってて、onRestoreInstanceStateEditText の状態が復元される際に TextWatcher でイベントを捕捉して Presentation Model 側の状態も変える、みたいな。
  • Android フレームワークの挙動をちゃんと理解しておけばなんとでもなるのだけど、ちゃんとりかいしておかないと変なところでハマる。
  • 個人的には Activity と View を疎に保ちたいのだけど、Android フレームワーク的には結構べったりくっついてるので辛い。

ThreeTenABP と ThreeTenBP の関係について (Android における JSR-310 バックポート)

「ThreeTenABP って Android フレームワークに依存するけど、ドメインレイヤとかで Android 依存を排除したい場合どうするのがいいんですかね」 という相談を受けて、ThreeTenABP 周りを調べたのでメモ。

ThreeTenABP って何?

JSR 310 Date and Time API 周りの話

ThreeTenABP が存在する意味は?

  • ThreeTen Backport (threetenbp) があるならそれでいいのでは? → ThreeTen Backport はタイムゾーン周りの情報を JAR から読み込むという仕組みになっており、Android では非効率。

というわけで

ThreeTenABP を使いたいけど Android フレームワークに依存させたくないというモジュールでは、ThreeTen Backport に依存させて、テストを書く際にタイムゾーン情報の扱いだけなんとかする、みたいなことをすれば良さそう。

タイムゾーン情報周りを厳密にテストしなくていいなら、compileOnly "org.threeten:threetenbp:${threetenbp_version}"testCompile "org.threeten:threetenbp:${threetenbp_version}" を依存に追加しちゃうのが手軽でいい気がする。 (バージョン周りはよしなに。 これでうまくいくだろうと思って書いてるけど特に何も確認はしてない。)

関連ページ

Android のアカウントマネージャ (AccountManager) の概説

最近 Android のアカウントマネージャを仕事で触ったので、調べた内容としてアカウントマネージャの概要をまとめておきます。

Web 上を調べると AccountManager を使う処理の実装方法はいろいろ見つかるのですが、アカウントマネージャの概要を説明しているページはあんまりなくて全体像を掴みにくいと思っています。 そういう情報を探している人の役に立てば幸いです。

公式ドキュメント

アカウントマネージャに関する公式ドキュメントは以下のものぐらいしかなさそうです。 AccountManager クラスのドキュメントを全て (各メソッドの説明も含めて) 読むと大体理解できると思います。 (下記ドキュメントを見て理解したら本ページの内容を読む必要はありません。)

アカウントマネージャの概要

アカウントマネージャは、Android のシステムがユーザーのオンラインアカウントを中央管理するための機能です。 Android 端末の 「設定」 にずらずらとアカウントが並ぶと思いますが、これもアカウントマネージャの仕組みで実現されています。

f:id:nobuoka:20170308031030p:plain:w360

各種オンラインサービスはそれぞれ異なったアカウントの扱い方や認証方式を使うため、アカウントマネージャはアカウント種別ごとに authenticator モジュールを使い分けるようになっています。

使いどころ

複数アプリで認証情報を共有するときに便利です。 (認証情報の提供側、および利用側両方とも。)

また、単一アプリでのみ認証情報を使う場合でも便利に使えると思います。 なぜなら、アカウントマネージャを使うことで認証周りの処理を分離しやすくなり、また、統一された方法で認証トークンを扱えるようになるためです。

用語

  • Account Type : Authenticator を特定するための文字列。
  • Auth token (認証トークン) : オンラインサービスのユーザー認証に使われるトークン。
  • Auth token type : Authenticator 固有の、Auth token 種別。 1 つのアカウントが auth token を複数持つことができる。
  • Feature : Authenticator 固有の、アカウントプロパティを特定するトークン。
  • Client (クライアント) / Application : アカウントマネージャを使って認証情報を取得するアプリを指す。
  • Authenticator : トークンの払い出しなどを行うやつ。 実体は AbstractAccountAuthenticator を継承したクラス。
    • システム上で有効な authenticator (AbstractAccountAuthenticator) を持っているアプリを指して Authenticator と言ったりもしてる? (明示的にアプリを表す場合は 「Authenticator アプリ」 と呼ぶのが良さそう。)
    • クライアントであり、かつ Authenticator であるというようなアプリも存在しうる。

Authenticator とクライアントの関係

アカウントマネージャを扱う処理を大別すると、authenticator とクライアントに分かれます。

  • Authenticator (や、それに関連する処理) : オンラインサービスのアカウント取得や認証処理を行います。 典型的には、ユーザーに対してログイン画面を表示してユーザー名とパスワードを入力させ、それらのアカウント情報を使ってオンラインサービスから認証情報を取得して、アカウントマネージャに渡します。 普通はそのオンラインサービスの提供者がアプリの中に実装します。
    • Authenticator のメソッドはアカウントマネージャから呼ばれます。
  • クライアント : アカウントマネージャを通じて、オンラインサービスの認証情報 (アクセストークンなど) を取得し、それを使ってオンラインサービスにリクエストを投げる、というような処理です。
    • 自社サービスクライアント : 自社が提供するサービスの認証情報をアカウントマネージャから取得します。 このアプリのシグネチャは、対応する authenticator を含むアプリのシグネチャと同じになります。
    • 他社サービスクライアント : 他社が提供するサービスの認証情報をアカウントマネージャから取得します。 このアプリのシグネチャは、対応する authenticator を含むアプリのシグネチャと異なります。

クライアントが AccountManager のクライアント向けのメソッドを呼ぶと、(一部のメソッドは) 対応する Authenticator のメソッドを呼びます。 そして、典型的には Authenticator のメソッドは AccountManager の Authenticator 用のメソッドを呼びます。 クライアントも Authenticator も AccountManager のメソッドを呼ぶという点で混乱しやすいのですが、AccountManager のメソッドをクライアント用メソッド群と Authenticator 用メソッド群に分けると、理解しやすいと思います。

f:id:nobuoka:20170308034440p:plain

自社サービスクライアントと他社サービスクライアントについて公式ドキュメント上での区別はありませんが、こういう分け方で考えると理解しやすいと思うので、本ページではこのように表記します。 ちなみに厳密な区別としては、「扱うアカウント種別の authenticator アプリと同じシグネチャを持つアプリ」 が自社サービスクライアント、「異なるシグネチャを持つアプリ」 が他社サービスクライアントです。

AccountManager のメソッド一覧

アカウントマネージャについて理解するには AccountManager クラスに生えている全てのメソッドを把握すればいいのですが、ドキュメントそのままだと理解しづらいので、整理して一覧できるようにしておきます。 これらのメソッド一覧に目を通すと、アカウントマネージャについての理解が深まるはずです。

API level 25 のドキュメントを参照しています。

クライアント向け

他社サービスのクライアントが使えるメソッド

Authenticator 一覧を取得したり、アカウント一覧を取得したり (パーミッションがあれば他社サービスのアカウントも全て)、指定のアカウントの認証トークンを取得したり、指定の認証トークンが無効になっていることをアカウントマネージャに伝えたり、ユーザーが指定のアカウントのパスワードを知っていることを確認するよう依頼したり、といったことができます。

  • getAuthenticatorTypes()
  • addOnAccountsUpdatedListener
    • アカウントマネージャが管理するアカウントの変化 (追加や削除など) を検知するリスナ (OnAccountsUpdateListener) を追加する。
    • getAccounts メソッドで取得可能なアカウントの変化のみ検知できる。 (典型的には、任意のアカウントの変化を検知するには GET_ACCOUNTS パーミッションが必要。)
  • removeOnAccountsUpdatedListener(OnAccountsUpdateListener)
    • リスナの削除。
  • addAccount
    • 指定のアカウント種別のアカウントを追加するよう依頼する。 対応する Authenticator が適切な UI でこのリクエストを処理する。
    • API level 22 以下では MANAGE_ACCOUNTS パーミッションが必要。
  • アカウント取得系
    • getAccounts
      • バイスに登録されているすべてのアカウントのリストを取得する。
      • ただし、GET_ACCOUNTS パーミッションがない場合は、同じシグネチャの Authenticator が管理するアカウントしか取得できない。
      • API level 22 以下の場合は GET_ACCOUNTS パーミッションが必要。 (ドキュメントに明記されてないけど多分。)
    • getAccountsByType
      • バイスに登録されているアカウントのうち、指定のアカウント種別のものを取得する。
      • GET_ACCOUNTS パーミッションがない場合は、同じシグネチャの Authenticator が管理するアカウントのみ取得できる。
      • API level 22 以下の場合は GET_ACCOUNTS パーミッションが必要。
    • getAccountsByTypeAndFeatures
      • バイスに登録されているアカウントのうち、指定のアカウント種別で、指定の機能をもつものを取得する。
      • 機能を考慮しないアカウント取得のメソッド (上 2 つ) とは違い、Authentcator が処理を行う。 (機能の管理をするため。)
      • GET_ACCOUNTS パーミッションがない場合は、同じシグネチャの Authenticator が管理するアカウントのみ取得できる。
      • API level 22 以下の場合は GET_ACCOUNTS パーミッションが必要。 (ドキュメントに明記されてないけど多分。)
  • confirmCredentials
    • ユーザーが指定のアカウントのパスワードを知っていることを確認する。 対応する Authenticator がこのリクエストを処理する。
    • 買い物処理みたいな、重要な操作の前に呼び出す、というような使われ方だと思う。
    • 呼び出し側がパスワードを渡した場合はそれが使われて、そうでなければ Authenticator 側がパスワード入力画面を表示する必要があるっぽい。
    • API level 22 以下では MANAGE_ACCOUNTS パーミッションが必要。
  • updateCredentials(Account, String, Bundle, Activity, AccountManagerCallback<Bundle>, Handler)
    • ユーザーに対して、新しいパスワードを入力してもらうよう依頼する。 対応する Authenticator がこのリクエストを処理する。
    • 保存されているパスワードを更新するため? 使いどころをあんまりわかってない。
    • API level 22 以下では MANAGE_ACCOUNTS パーミッションが必要。
  • getPreviousName
    • 指定のアカウントの前の名前を返す。
    • LOGIN_ACCOUNTS_CHANGED_ACTION ブロードキャストを受け取ったときに、アカウント名が変更されているかどうか検知できるようにするためのメソッドらしい。
  • Auth token 取得系 : AccountManager がキャッシュしていたらそれを返し、なければ対応する Authenticator が認証トークンを取得する。
    • getAuthToken(Account, String, Bundle, boolean, AccountManagerCallback<Bundle>, Handler)
      • バックグラウンドの処理向け。
      • 指定のアカウントの指定のトークン種別の認証トークンを取得する。 アカウントマネージャがキャッシュしている場合はそれが返される。 キャッシュされていない場合は authenticator がリクエストを処理する。
      • Authenticator は、パスワードが使えるのであればそれでサーバーに問い合わせを行う。 それが無理ならユーザーにログイン画面を表示して、ユーザー入力によって認証トークンを取得する。
      • notifyAuthFailure に真を渡したならば、そのインテントを開始するステータスバーの通知も表示される。
      • その場合 (ログインする必要がある場合? それともステータスバーの通知が表示される場合?) には、ユーザーが対応するまで何時間も、何日も、あるいは永遠に待つことになる可能性がある。
      • notifyAuthFailure に偽を渡した場合は、アプリケーションが Intent を開始する責任がある。
      • API level 22 以下では USE_CREDENTIALS パーミッションが必要。
    • getAuthToken(Account, String, boolean, AccountManagerCallback<Bundle>, Handler)
      • API level 14 で Deprecated になってるので Bundle パラメータ有り版を呼ぶこと。
    • getAuthToken(Account, String, Bundle, Activity, AccountManagerCallback<Bundle>, Handler)
      • フォアグラウンドで動いている処理向け。
      • 指定のアカウントの指定のトークン種別の認証トークンを取得する。 アカウントマネージャがキャッシュしている場合はそれが返される。 キャッシュされていない場合は authenticator がリクエストを処理する。
      • API level 22 以下では USE_CREDENTIALS パーミッションが必要。
    • blockingGetAuthToken
  • getAuthTokenByFeatures(String, String, String[], Activity, Bundle, Bundle, AccountManagerCallback<Bundle>, Handler)
    • 他のメソッド (getAccountsByTypeAndFeaturesgetAuthTokenaddAccount) の処理を組み合わせた便利メソッド。
    • API level 22 以下では MANAGE_ACCOUNTS パーミッションが必要。
  • invalidateAuthToken
  • hasFeatures
    • 指定のアカウントが指定の全ての機能を持っているかどうかを調べる。
    • 呼び出し側は GET_ACCOUNTS パーミッションを持っているか、もしくは Authenticator と同じシグネチャである必要がある。
自社アプリ (同じシグネチャを持つアプリ) のみ
  • removeAccount(Account, Activity, AccountManagerCallback<Bundle>, Handler)
    • アカウントマネージャからアカウントを削除する。 Authenticator は固有のポリシーによってアカウント削除をしないという選択をしてよい。
    • 呼び出し側は Authenticator アプリと同じシグネチャを持つ必要がある。
    • API level 22 以下では MANAGE_ACCOUNTS パーミッションが必要。
  • removeAccount(Account, AccountManagerCallback<Boolean>, Handler)
    • 上に同じ。 Deprecated。
  • renameAccount(Account, String, AccountManagerCallback<Account>, Handler)
    • 古いアカウントを削除し、古いアカウントのユーザーデータを持つ新しいアカウントを追加するのと同じ効果。 (Auth token はどうなるんだろ?)
    • 呼び出し側は Authenticator と同じシグネチャを持つ必要がある。
    • API level 22 以下では AUTHENTICATE_ACCOUNTS パーミッションと、authenticator と同じ UID である必要がある。
  • clearPassword
    • アカウントマネージャが保持しているパスワードを削除する。 「ログアウト」 ボタンの処理などで使われることを想定しているらしい。
    • setPassword メソッドでも同じことができるけどこっちの方が必要な権限は少ないっぽい。
    • アカウントの Authenticator と同じシグネチャが必要。
  • editProperties
    • Authenticator 固有の、Authenticator のプロパティ (アカウントごとではない) を編集する。 対応する Authenticator がこのリクエストを処理する? (未調査。)
    • 全ての Authenticator がこの機能を実装しなければならないわけではない。
    • Authenticator と同じシグネチャが必要。
    • API level 22 以下の場合は MANAGE_ACCOUNTS パーミッションが必要。

Authenticator (または、それに関連する処理) 向け

  • addAccountExplicitly(Account, String, Bundle)
    • アカウントをアカウントマネージャに追加する。
    • Authenticator に関連するサインアップ処理向け。
    • addAccount メソッドなどのシステムがアカウント追加を検知できるメソッド経由で呼ばれたわけじゃない場合は、自前で notifyAccountAuthenticated メソッドを呼ぶべき。
  • removeAccountExplicitly(Account)
    • アカウントをアカウントマネージャから削除する。
    • クライアント用のアカウント削除メソッドとは違い、Authenticator でのポリシー設定は見られないのだと思う。 (未調査。)
    • API level 22 以下では AUTHENTICATE_ACCOUNTS パーミッションと、さらに Authenticator と同じ UID である必要がある。
  • notifyAccountAuthenticated(Account)
    • アカウントが認証されたことをシステムに伝える。
    • このイベント情報が他のアプリで使われたりするっぽい? (未調査。)
    • 呼び出し側はアカウントの Authenticator と同じシグネチャを持つ必要がある。
  • getPassword
    • アカウントマネージャが保持しているパスワードを取得する。
    • 呼び出し側は Authenticator と同じシグネチャを持っている必要がある。
    • API level 22 以下では AUTHENTICATE_ACCOUNTS パーミッションが必要。
  • setPassword(Account, String)
    • アカウントマネージャにパスワードを保存する。 (あるいは削除する。)
    • Authentiator と同じシグネチャを持つ必要がある。
    • API level 22 以下では AUTHENTICATE_ACCOUNTS パーミッションと、さらに Authenticator と同じ UID である必要がある。
  • getUserData
  • setUserData(Account, String, String)
  • peekAuthToken(Account, String)
    • AccountManager のキャッシュから auth token を取得する。 なければ null が返される。
    • API level 22 以下では AUTHENTICATE_ACCOUNTS パーミッションが必要で、さらに Authenticator アプリの UID と同じ UID を持っている必要がある。
  • setAuthToken(Account, String, String)
    • Auth token をアカウントマネージャのキャッシュに追加する。
    • Authentiator と同じシグネチャを持つ必要がある。
    • API level 22 以下では AUTHENTICATE_ACCOUNTS パーミッションと、さらに Authenticator と同じ UID である必要がある。

その他

関連ページ

読んだ : Kotlin スタートブック ― 新しい Android プログラミング / 長澤 太郎 著

Kotlin エバンジェリスト (JetBrains 黙認) であり、日本 Kotlin ユーザグループ代表であるたろーさん (長澤 太郎) によって書かれた書籍 『Kotlin スタートブック ― 新しい Android プログラミング』 (赤べこ本) を読みました!!

Kotlinスタートブック -新しいAndroidプログラミング

Kotlinスタートブック -新しいAndroidプログラミング

本書を読む前から、公式のリファレンスチュートリアルなどの気になる箇所を見てはいて Kotlin についてはなんとなく理解はしていたのですが、ところどころ知識が欠けている部分もあったので、本書によってそういった知識の欠落を埋めることができました。

どんな本か?

内容については著者のたろーさんが紹介されています。

導入としてまず 1 部があり、2 部で Kotlin の文法や機能の詳細が解説されます。 3 部にて、Android アプリ開発で Kotlin をどのように活用できるかが説明されます。 対象読者は 「Java および Android 開発経験者」 と書かれていますが、2 部までなら Android アプリ開発については知らなくても問題なく読み進められます。 「Android アプリ開発で Kotlin を使うために読む本」 というわけではなく、「Kotlin を使い始めるときに読む本」 かつ 「Android 開発に Kotlin を活用するためにどうすればいいかが書かれている本」 という感じです。

全体としては易し目の説明でスクリーンショットも多用されているのでプログラム開発の初心者でも読みやすい本だと思います。 一方で、説明する内容についてきちんと節が分かれていて飛ばし気味に読むこともやりやすいので、ソフトウェア開発についてある程度経験がある人が Kotlin の導入のために読むというのにも適しています。

感想

Kotlin を Java の代わりとして使い始めるのは結構簡単で、いくつかある Java との違い (各種文法の違いや 「== 演算子は同一性検査ではない」 みたいな違い) をおさえさえすれば導入できます。 逆に導入するのが簡単なせいで、「ちゃんと Kotlin 特有の機能や文法を学ぶ」 機会を設けず、必要に応じて公式リファレンスを参照している、という人も多いのではないでしょうか。 私もまさにそんな感じだったので、今回本書を読んで Kotlin 特有の機能、文法を学べたのは非常に良かったです。

内容的には深く突っ込んでいくという感じではないので、本書を読みつつ気になったところは公式リファレンスを参照する、みたいに読み進めていくといい気がします。 公式リファレンスとは違った視点での説明されている項目もあったりするので、両方参照することで理解が深まったりもすると思います。 公式リファレンスには書かれていない 『!! 演算子の代わりに requireNotNull メソッドを使うと良い』 というような記述があるのも本書の良いところですね。

まさに 「これから Kotlin を使っていこうと思っている人のための Kotlin 入門本」 として、以下のような人にオススメできる書籍でした。

  • Kotlin を使ってみてるけどまだ Kotlin 全体についてちゃんとは学んでいない。
  • まだ Kotlin を使ってみてないけどこれから使ってみようかと思っている。

第 3 部の内容 (Kotlin で Android アプリ開発) について、ほとんど知っているものだったので自分にとってはあまり意味はなかったのですが、Kotlin を使った Android アプリ開発がどんな感じか全然知らない人にとってはそれらも役立つと思います。

読書メモ

自分にとって気になった箇所と、それに関連する公式リファレンスへのリンク。