Android RecyclerView ページングリスナーを自作する

こんにちは、お久しぶりです。

今回はRecyclerViewのページングリスナーを自作したので共有します。
どうしてJetpack Paging Libraryを使わないのかという経緯もご説明します。

コード

とりあえず全体のコードを先に載せてしまいます。
すべてのLayoutManagerに対応しています。
特にStaggeredGridLayoutManagerは結構苦労しました。
また、任意にページング取得開始位置なども設定できるのでよしなに変えてください。

  • LinearLayoutManager
  • GridLayoutManager
  • StaggeredGridLayoutManager

gist98be509fb1fa74dcc73e278085c87964

あとは、recyclerViewのaddOnScrollListenerで追加してあげます。

Jetpack Paging Libraryを使わない経緯

Jetpack Paging Libraryは便利です。
実際に上記のコードを実装しなくてもページングを実現できます。

しかし、アイテムの要素を更新したい要望を実現するにはJetpack Roomを同時に使う必要があります。
ページングのためだけにデータ層まで変えないといけないのか??

これが非常に重荷だったので、今回は独自に作ることにしました。
とりあえず取得だけして表示だけするようなリストならJetpack Paging Libraryでも問題ないと思います。

以上です〜

コピペで使える!!ブランチ名をコミット名に自動付与

f:id:QoopMk:20200219095133p:plain

はじめに

コミットにチケット名入れるの自動化したので共有します。

たとえばこんな感じにします。

作業ブランチ
feature/issue/#9

コミットメッセージ
xxxの実装

↓コミット

コミット名
[#9] xxxの実装

準備

コミットにブランチ名を入れるために、git-hook機能を使います。

以下のコマンド実行してcommit-msgというファイルを作成します。

$ touch <repo>/.git/hooks/commit-msg
$ chmod +x <repo>/.git/hooks/commit-msg

commit-msgファイル編集
スラッシュ区切りの最後の文字列を入れてるだけです。
アンダーバーとかに変更したい場合は任意に変更してください。

#!/usr/bin/env ruby
message_file = ARGV[0]
message = File.read(message_file, :encoding => Encoding::UTF_8)
# remove prefix issue number like [#1234] from COMMIT_EDITMSG
message = message.sub(/^\[#[0-9A-Za-z_].*\]/, "")
# remove comment
message = message.gsub(/^#(?! ------------------------ >8 ------------------------).*\n|^\n/, "")
if message =~ /(?=\A)# ------------------------ >8 ------------------------\n/
  puts "An empty message aborts the commit."
  exit 1
end
# get last path of current branch
current_branch = `git branch | grep '*'`.chomp.sub('* ', '')
current_branch = current_branch[current_branch.rindex("/")+1 .. current_branch.length] # スラッシュ区切りにしてるが任意の文字に変えることも可能
# add [last path of current branch] to commit message
newmessage = "[#{current_branch}] #{message}" # コミットメッセージ作成
File.write(message_file, newmessage)

以上です

Androidの強制アップデートでよく使うアプリバージョン比較方法

現在のバージョンと強制アップデートのバージョンを比較して、処理をするということはよくあります。
重大なバグがあった場合に「このバージョン以上は〜する」みたいなときに使うあれです。

おまけ

こんなけバージョン比較方法を紹介しといてあれなんですが
最近は in-app-updates という機能が追加されました。

ただこれはアプリ内で、アップデートを促進するための機能です。
アップデート画面を閉じることができるので 強制アップデートではないです。

Android UIの実装において意識してほしいこと

概要

今回はUIを実装するにあたって常に意識してほしいことをまとめました。
ここでいうUIとは主にレイアウト(xml)のことを指しています。

UIといってもユーザーエクスペリエンスの話がどうという話をするわけではないです。あくまでデザイナーではなく開発者がレイアウトを作成する上でやったほうがいいこと挙げてみました。

UI Stackを意識する

「UI Stack」とは1つの画面が状態に応じて、最適化された表示を行うというものです。 というと難しいかもしれませんが、要は「ローディング状態」や「空っぽの状態」などをうまく切り替えましょうということです。

f:id:QoopMk:20191115222341j:plain

これはアメリカのデザイナー Scott Hurffさんによって提唱されました。こちらが元ネタです。
https://www.scotthurff.com/posts/why-your-user-interface-is-awkward-youre-ignoring-the-ui-stack/

この「UI Stack」は5つの状態から構成されています。

  • Blank State(空っぽの状態)
  • Loading State(ローディング状態)
  • Partial State(部分達成状態)
  • Error State(エラー状態)
  • Ideal State(理想状態)

それぞれの解説についてはこちらの方の記事が参考になりました。

note.mu

しかし、多くのデザインはIdeal State(理想状態)のものしか用意されてないことが多いことが現実です。
あらかじめ状態を切り替えれるようにロジックを組んでいれば、後からでもデザイナーと相談して作成していくことができるので、1つの画面ごとに状態の変化があるということを意識して実装していきましょう!

Tools-Attributesを正しく設定しよう

Tools-Attributesとはレイアウトを組む際にPreviewに表示される、いわば仮のデータです。
これを設定していない人はかなり多いです。

Tools-Attributesを正しく設定することで、本物のデータを反映した時と近しい画面をPreviewで確認することができます。
これをやっていないと、予期せぬところでTextViewの改行が許容されていたり、画像の表示エリアが想定していたエリアと異なっていたなどの問題が発生してしまいます。
なので、固定値がある場合はいいですが、コードで反映する変数値のUIは必ず可視化しておくことが大切です。頭で考えるよりも見えていた方が何倍も楽です。

特に以下のものは重要です。

View

  • tools:visibility
    • 必須ではないが、UIが重なっている かつ ステータスに応じて表示・非表示となる場合は必須。1つだけをVISIBLEに設定して、それ以外はGONEもしくはINVISIBLEに設定。

RecyclerView

  • app:layoutManager
    • 必須。LinerLayoutManager/GridLayoutManager/StaggeredGridLayoutManagerのいづれかを設定。
    • コードで設定する必要がある場合は tools:layoutManager でも可。
  • tools:spanCount
    • 必須ではない。ただし、GridLayoutManager使用する場合は必須。
  • tools:itemCount
    • 必須ではない。
  • tools:listitem
    • 必須。

TextView

ImageView

ビルドしてみると正しく表示はされているけど、Preview開くとなにも表示されていないというのは実装しにくさこの上ない。

Android Toastは使うべきではない

f:id:QoopMk:20191107160955p:plain

developer.android.com

つい最近 Toast が表示されないというバグを見つけたので共有します。

AndroidのIssueにも起票されていました。

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

端末の設定で通知をOFFにしてしまっていると発生します。

Toastを表示させる関数を見てみると、通知が有効になっているかの判定を行なってしまっているようです。

areNotificationsEnabledForPackageInt の部分。

public void enqueueToast(String pkg, ITransientNotification callback, int duration)
    {
        if (DBG) Slog.i(TAG, "enqueueToast pkg=" + pkg + " callback=" + callback + " duration=" + duration);

        if (pkg == null || callback == null) {
            Slog.e(TAG, "Not doing toast. pkg=" + pkg + " callback=" + callback);
            return ;
        }

        final boolean isSystemToast = ("android".equals(pkg));

        // ここが問題
        if (ENABLE_BLOCKED_TOASTS && !isSystemToast && !areNotificationsEnabledForPackageInt(pkg)) {
            Slog.e(TAG, "Suppressing toast from package " + pkg + " by user request.");
            return;
        }
        ....略
    }

補足

現在はすでに修正されているので、Android10では発生しないようですが、いずれにせよ下位バージョンを対応させることのほうが多いので、SnackBarやアラートなどで代替したほうがよさそうです。

https://android.googlesource.com/platform/frameworks/base.git/+blame/f02b60aa4f367516f40cf3d60fffae0c6fe3e1b8/services/java/com/android/server/NotificationManagerService.java#660

ソースコードなんて綺麗に書く必要はない

はじめに

私は以前まで、受託会社でスマホアプリを開発していましたが、独立してから自社事業に多く参画することになりました。
同じエンジニアリングでも受託会社からのマインドを一新する必要がありました。
それらの経験を経て感じたことを話したいと思います。

ソースコードなんて綺麗に書く必要はない

受託開発になるとソースコードに対して、ある程度品質を担保する必要があります。
理由は他にもありますが、一つ例を挙げると、開発する会社と運用する会社が異なるケースが存在するからです。
家に例えると、ハウスメーカーである建設会社とリフォームなどを行う地域の工務店みたいなものです。家を建てるのは建設会社ですが、リフォームを行うのは工務店です。
リフォームを行なっている際に、欠陥住宅であるということがバレると建築会社の信頼に関わるので、顧客は二度とその会社には依頼しないでしょう。
つまり、再発注をもらえなくなるリスクがあります。

f:id:QoopMk:20191107015116j:plain

極端な話、自社の新規サービスを構築する場合はそんなことどうだっていいのです。
ソースコードが汚くても、サービスが正常に使えるのであれば、ユーザーにとっては関係のない話だということです。
ソースコード内の変数をわかりやすい名前にするのってエンジニアの自己満であり、サービス全体を俯瞰としてみると小さな問題です。

もちろんマインドの話をしているのであって、汚いソースコードを書いてもいいって話をしているわけではないです。
例えば、何気なく行なっている言語を採用する際に関しても「このフレームワークは、完結にソースコードを書くことができるから採用しよう」ではなく、「このフレームワークを使うことでソースコードを完結に書くことができ、全体的な開発コストが下がり、ユーザーにサービスを安く提供できる」というところまで考えることが重要であるということです。

常にユーザーの利点に繋がるように考えていれば、1つの判断の軸になるかもしれません。

Kotlin 正規表現はStringの拡張関数でカプセル化しておく

はじめに

今回は、正規表現に関するおすすめ対処法です。
正規表現で文字列を判定する場合に、Utility・Helperなどのクラスを作成するのはよくみられます。

間違いではないので、別に悪いことではないのですが、Utility・Helperなどのクラスは役割がどの範囲なのかということがあやふやになってしまいがちなので、個人的にはExtensionで作成することが多いです。

これだと、Stringに対する判定なのか、Intに対する判定値なのかということが明確になりますし、Stringオブジェクトからそのまま実行することができます。
また、正規表現が1つにまとめられるので電話番号はハイフンなくすといった変更にも対応しやすいです。

ただ、この辺りは論争になりそうな雰囲気もありますが、、

/**
 * メールアドレスの有効判定
 *
 * @return Boolean
 */
fun String.isEmail(): Boolean {
    return Patterns.EMAIL_ADDRESS.matcher(this).matches()
}

/**
 * 電話番号の有効判定
 *
 * @return Boolean
 */
fun String.isPhoneNumber(): Boolean {
    val regex = Regex("[0-9]{10,11}|[0-9]{2,4}[- ][0-9]{1,4}[- ][0-9]{4}")
    return regex.matches(this)
}

/**
 * 郵便番号の有効判定
 *
 * @return Boolean
 */
fun String.isZipcode(): Boolean {
    val regex = Regex("[0-9]{3}-?[0-9]{4}")
    return regex.matches(this)
}

おまけ

パスワードの入力チェックなどもこのようにしておくことがあります。
正直、認証基盤の仕様によるのでExtensionにするのは如何なものかという意見もありますが、入力判定ロジックは可能な限りまとめておきたいのでこのようにすることもあります。

/**
 * パスワードの有効判定
 *
 * @return Boolean
 */
fun String.isPassword(): Boolean {
    return length in PASSWORD_MIN_LENGTH..PASSWORD_MAX_LENGTH
}

Android ImageViewのTintColorを変更する

Xmlから変更

android:tint="@color/hoge"

コードから変更

ImageViewのTintColorをコードから変更したい場合バージョンごとに差異があるのでメモです。
API21以上向けの場合は以下のようにしておけば問題なしです。

/**
 * 画像のTintColorを設定
 *
 * @param color カラー
 */
fun ImageView.setTintColor(@ColorInt color: Int) {
    ImageViewCompat.setImageTintList(this, ColorStateList.valueOf(color))
}

/**
 * 画像のTintColorを設定
 *
 * @param resId カラーリソースID
 */
fun ImageView.setTintColorRes(@ColorRes resId: Int) {
    val color = ContextCompat.getColor(context, resId)
    ImageViewCompat.setImageTintList(this, ColorStateList.valueOf(color))
}

モックAPIはjson-serverじゃなくてJSONPlaceholderを使おう

f:id:QoopMk:20191101184556j:plain

はじめに

以前、 json-server という npmパッケージ を紹介しました。
Jsonを用意するだけで、一通りのCRUDを再現できるという便利なものでした。
しかし、これだけのためにnpmパッケージを管理しなくちゃいけないのかという問題があります。

ちなみに、前回の記事はコチラです。

qoopmk.hatenablog.jp

今回は、そんなjson-serverをより使いやすくしたものを紹介します。
「JSONPlaceholder」というjson-serverを使用したモックAPI作成アプリケーションです。

jsonplaceholder.typicode.com

デフォルトでいくつかAPIを用意していただいているみたいですね〜。
とりあえずデータはなんでもいいって人はJsonファイルは用意せずに、おとなしくこれ使いましょう。

/posts   100 posts
/comments   500 comments
/albums 100 albums
/photos 5000 photos
/todos  200 todos
/users  10 users

Jsonファイルを用意

json-server同様に、データベースを定義したJsonファイルを用意する必要があります。

{
  "posts": [
    { "id": 1, "title": "json-server", "author": "typicode" }
  ],
  "comments": [
    { "id": 1, "body": "some comment", "postId": 1 }
  ],
  "profile": { "name": "typicode" }
}

GitHubに配置

先ほど作成したJsonファイルををGitHubのRepositoryに置くだけで完了します。

f:id:QoopMk:20191104080749p:plain

モックAPIをリクエス

GitHubJsonファイルの配置が完了すると、以下のエンドポイントにリクエストすれば自動的にモックAPIが起動してくれます。
オリジナルのjson-serverと違ってhttps対応もしてくれているのでありがたいです!!

https://my-json-server.typicode.com/<your-username>/<your-repo>

まとめ

内部はjson-serverと同等の機能なので、前回の記事通りモック複雑化してくると若干の使いにくさはでてきますが、簡易的に通信を試してみたいということであれば、物足りると思います。
開発はどんどん効率化していきましょう!!

モックAPIをjson-serverで作成する

f:id:QoopMk:20191101184556j:plain

はじめに

設計段階などの検証用途・通信実装を実装してみるなどの学習用途で、通信が絡むモックAPIが必要になる場合があると思います。
その場合にモックAPIを簡易的に作成できないものかと調査したので共有します。

インストール

今回はnpmパッケージを使用して、json-serverをインストールしてください。

$ npm install json-server

npmの設定方法についてはコチラ

qoopmk.hatenablog.jp

github.com

Jsonファイルを用意

まずはデータベースの構造を定義する Jsonファイルが必要になります。
これを json-server が読み取ってモックAPIを自動で作成してくれます。

仮に名前を db.json としてこんな感じで定義してみましょう。

{
  "posts": [
    { "id": 1, "title": "json-server", "author": "typicode" }
  ],
  "comments": [
    { "id": 1, "body": "some comment", "postId": 1 }
  ],
  "profile": { "name": "typicode" }
}

モックAPIサーバー起動

あとは以下のコマンドを実行してモックAPIサーバーを起動しましょう。

$ $(npm bin)/json-server --watch db.json --port 3000
  • $(npm bin) はローカルのnpmパッケージのbinのパスを出力してくれますので、そのまま打ち込んでください。
  • --watch オプションは手動で db.json を修正した時にもAPIに反映してくれるという機能を有効にします。
  • --port オプションでポート番号も変更可能です。

モックAPIをリクエス

実際にリクエストしてみましょう。
レスポンスが返ってこれば成功です!

$ curl "http://localhost:3000/posts"

GETだけでなく、POSTやDELETEなども使えるのが json-server の魅力です。

補足

コチラでnpmパッケージを使わない方法も紹介しています!!

qoopmk.hatenablog.jp