ファイルツリー構造を表示 tree をインストール

f:id:QoopMk:20191101182342g:plain

はじめに

ファイルツリーの作成方法を紹介します。
ドキュメントを作成する時には重宝しています。

treeをインストール

Homebrewでtreeをインストールします。

$ brew install tree

出力

あとは、treeコマンドを入力すれば出力することができます。

# コマンド(パスがなければ現在のパスで出力されます)
$ tree app/src/main/kotlin/com/sample/hoge/

# 出力
app/src/main/kotlin/com/sample/hoge/
├── App.kt
├── MainActivity.kt
├── data
│   └── ApiClient.kt
├── di
│   └── AppModule.kt
└── ui
    └── main
        ├── MainFragment.kt
        └── MainViewModel.kt

意外に便利。

Ktlintのフォーマットをビルド時に実行する

はじめに

今回はKtlintのフォーマットをビルド時に実行するように設定していきます。

  • Ktlint + CI + Dangerを連携させてPRにコメントさせる
  • Ktlint + GitHookを使ってコミット時にフォーマットを実行する
  • AndroidStudioのGit管理機能でコミット時にフォーマットを実行する

これらの方法は紹介されていましたが、SwfitLintのようにRunScriptで実行されるのは魅力だったのでAndroidでも同じようにできないかと試行錯誤してみました。
当ブログでもそのうちそれぞれのやり方を比較する記事を書こうかと思います。

導入

まずは、公式ドキュメントにしたがって導入していきます。

ktlint.github.io

今回は、ktlint用の ktlint.gradle を分けて作成しているので、ファイルツリーも一応載せておきます。

├── app
│    ├── build.gradle
├── gradle
│    ├── ktlint
│    │     └── ktlint.gradle
├── .editorcondig

ktlint.gradle

configurations {
    ktlint
}

// ktlintの設定
task ktlint(type: JavaExec, group: "verification") {
    description = "Check Kotlin code style."
    classpath = configurations.ktlint
    main = "com.pinterest.ktlint.Main"
    args "src/**/*.kt",
            "--android",
            "--color",
            "--reporter=plain",
            "--reporter=checkstyle,output=${buildDir}/reports/ktlint-results.xml"   // レポート出力
    ignoreExitValue true    // CIで異常終了させない
}
check.dependsOn ktlint

// ktlintのフォーマット設定(自動修正)
task ktlintFormat(type: JavaExec, group: "formatting") {
    description = "Fix Kotlin code style deviations."
    classpath = configurations.ktlint
    main = "com.pinterest.ktlint.Main"
    args "-F", "src/**/*.kt"
}

build.gradle

apply from: '../gradle/ktlint/ktlint.gradle'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    // Lint
    ktlint "com.pinterest:ktlint:0.35.0"
    ...
}

Ktlintの設定

ktlintは最初に.editorconfigのスタイル設定を読み込みに行くため、そこに独自のスタイルを明記しておけば、そのスタイルに従ってlintがかけられます。

// 例
[*.{kt,kts}]
indent_size=2
continuation_indent_size=4
insert_final_newline=unset
max_line_length=off

ビルド時に自動フォーマット

ktlint.gradle

先ほど作成した ktlint.gradle に以下のタスクを追加してください。

// ktlintのフォーマットをビルド時に実行
task ktlintFormatRun(type: JavaExec) {
    "./gradlew ktlintFormat".execute()
}

※ もっと良い方法があったら教えてください。

まとめ

若干ビルドが遅くなるのが難点です。

npmのインストール

npmインストール

$ brew install npm

npmの初期設定

$ npm init

npmの読み込み

  • プロジェクトにインストールする
$ npm install
  • グローバルにインストールする
$ npm install -g

.gitignore

  • npm install で package.json に示されているパッケージがインストールされるので、Gitにはpackage.json のみ管理すれば良い

qiita.com

Kotlinのコーディング規約

はじめに

今回は、Kotlinのコーディング規約作成しなきゃと追われている人たちに贈ります。

Kotlin公式コーディングスタイル

コーディング規約はJetBrainsがご丁寧に書いてくれています。
プロジェクトごとに規約考えるのではなく、素直にしたがっておくのがいいと思います。

Coding Conventions - Kotlin Programming Language

Android公式コーディングスタイル

  • TODO:追加していく

それ以外

  • TODO:追加していく

AndroidStudioフォーマットをマクロで登録する

はじめに

今回は、AndroidStudioでフォーマットのマクロを組む方法を紹介します。
これを設定しておくと、インデントが揃っていなかったり不要なスペースが入っていたり不要なインポートがされているということはおおよそ防ぐことができます。
チームないでコード規約を登録しておくと毎回意識しなくて済みますね!
Ktlintでもできるので、それはそのうち書くことにします。

最終的にはこのように一瞬でフォーマットされるマクロを作成することを目指します。

f:id:QoopMk:20191028131153g:plain

マクロのレコーディングを開始

まずはマクロを登録するためにマクロのレコーディングを開始しましょう。

f:id:QoopMk:20191028130942p:plain

このように、右下に「Macro Recording Started...」と表示されていれば開始されています。

f:id:QoopMk:20191028130945p:plain

コードをフォーマット

今度は実際にマクロを登録するために手順を踏んでいきます。順番が入れ替わってしまうと正しくフォーマットされないので気をつけてください。

Step1

「Optimize Imports」を実行してください。 これを実行することで不要なimportを削除してくれます。

f:id:QoopMk:20191028130948p:plain

Step2

「Reformat Code」を実行してください。 これを実行することで不要なインデントや空白を削除してくれます。

f:id:QoopMk:20191028130951p:plain

Step3

最後に「Save All」を実行して保存しておきましょう。

f:id:QoopMk:20191028130954p:plain

マクロのレコーディングを終了

マクロの設定は以上になるので、マクロを終了しましょう。

f:id:QoopMk:20191028130957p:plain

マクロの登録

マクロを終了させると、どんな名前で保存するか聞かれるので、とりあえず「Format and save all」としておきます。

f:id:QoopMk:20191028131000p:plain

マクロのキーを割り当てる

作成した「Format and save all」マクロにショートカットキーを割り当てましょう。

AndroidStudioのPreferencesを開いて検索バーに「Format and save all」と打ち込むとヒットすると思います。

f:id:QoopMk:20191028131003p:plain

f:id:QoopMk:20191028131006p:plain

表示されたマクロをタブルクリックすると、どのキーに割り当てるか尋ねられるので、「⌘S」を設定しましょう。
何でもいいので、自分の好きなキーに割り当ててください。

f:id:QoopMk:20191028131009p:plain

すでにキーが割り当てられていた場合に上書きしていいかアラートがでるので、入れ替わってよければ「Remove」を選択してください。

f:id:QoopMk:20191028131012p:plain

以上で設定完了です! 「⌘S」を押すだけでコードがフォーマットされます!

Android アプリ名を環境ごとに変更する

はじめに

今回はAndroidアプリで環境ごとにアプリ名を変更する方法をご紹介します。

どういったときに必要なのか

ここでいう環境というのは主にFlavorのことをさしています。
app/build.gradleで設定するあれです(説明雑)。

本番環境・開発環境・検証用などで分ける場合に、実機にアプリ名を見ただけて環境がわかると間違えて開発用でテストしてしまったり、なんてことも防げます。
実際に環境を選択してビルドしている開発者はほぼ間違えることがないかもしれませんが、それ以外の人がアプリ名を見ただけで判断できるとより安全です。

方法

今回は以下のようなアプリ名を設定してみます。

環境 アプリ名
本番環境 HogeApp
開発環境 HogeApp_dev
検証環境 HogeApp_stg

app/build.gradleのproductFlavorsの環境ごとに、resValueを使ってアプリ名を設定しましょう。
resValueで指定したものは string.xml に出力されます。(もともと string.xml に記載されている app_name は削除しておきましょう)

productFlavors {
    // 本番環境
    production {
        resValue "string", "app_name", "HogeApp"
    }
    // 検証環境
    staging {
        resValue "string", "app_name", "HogeApp_stg"
    }
    // 開発環境
    development {
        resValue "string", "app_name", "HogeApp_dev"
    }
}

以上!これだけでOKです!

最後に

Floverごとにフォルダを用意して string.xml を差し替える方法もありますが、非効率なのでこちらの方法をお勧めします!!
あと、FacebookSDKのように環境変数をBuildConfig ではなく string.xml で用意する必要がある場合はこれでやったほうが楽です。

Android File Transfer を利用してMacからAndroidのファイルを参照

はじめに

Android端末とMacでのファイルのやりとりについて紹介します。
いままで、MacからAndroidAndroidとMacのファイルの

Android端末とPC間でのファイル移動は、クラウドストレージや共有フォルダの利用、USBケーブルでの直接接続などいろいろな方法があると思います。

USBケーブルで直接接続してファイル移動する際、接続するPCがWindowsの場合はケーブルをつなぐだけで済んでしまう事が多いです。

接続するPCがMacの場合には、USBケーブルで接続しただけではAndroid端末内のフォルダが表示されないのですが、Macに「Android File Transfer」をインストールするとファイル移動できるようになります。

最近自分もMacAndroidをUSB接続してファイル移動する機会がありましたので、今回はその備忘録です。

インストール

f:id:QoopMk:20191026221026p:plain:w300

こちらからアプリをダウンロードしてください。

www.android.com

brew cask でもインストール可能です。

$ brew cask install android-file-transfer

補足

AndroidStudioでファイルを参照する場合はこちらをご確認ください!

qoopmk.hatenablog.jp

Androidエミュレータに画像を追加する方法はこちらを参照してください。

qoopmk.hatenablog.jp

Android RecyclerView で空の状態のViewを表示するAdapterDataObserverを作成

はじめに

今回は、RecyclerViewが空の状態に別のViewを表示する方法をご紹介します。
ListViewのEmptyViewみたいなものを想像していただければいいと思います。

ゴールとしては、RecyclerViewに setEmptyView(View) を呼び出すだけで設定できるまでを目指します。

Step1: レイアウトを作っていく

今回作成したのはシンプルに、「RecyclerView」と「RecyclerViewが空の場合に表示するView」の2つを用意しました。
ここでポイントになるのが、この2つは両方とも match_parent にしている部分です。VISIBLEの状態で正しく表示されないと正しく動作しないので、tools:visibilityなどをつかってしっかりとレイアウトを作成してください。

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:itemCount="6"
        tools:listitem="@layout/item_message"
        tools:visibility="gone" />

    <LinearLayout
        android:id="@+id/emptyView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:orientation="vertical"
        tools:ignore="UseCompoundDrawables"
        tools:visibility="visible">

        <ImageView
            android:layout_width="114dp"
            android:layout_height="100dp"
            android:src="@drawable/ic_message_placeholder"
            android:tint="#aaaaaa"
            tools:ignore="ContentDescription" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="16dp"
            android:text="メッセージはありません"
            android:textColor="#aaaaaa"
            android:textSize="14sp" />

    </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

Step2: RecyclerView AdapterDataObserver をカスタム

RecyclerViewにはアイテムの変更を監視してくれている、「AdapterDataObserver」というものがあります。
今回はこれを使用して、アイテムが0個になった時を検知していきましょう。

実際に判定しているのは checkIfEmpty() ですが、「初期化時」「アイテム挿入時」「アイテム削除時」にそれぞれ判定をいれています。
「初期化時」・・・EmptyViewが設定された時
「アイテム挿入時」・・・RecyclerViewにアイテムが追加された時
「アイテム削除時」・・・RecyclerViewからアイテムが削除された時

/**
 * RecyclerViewの空状態を監視
 *
 * @property recyclerView RecyclerView
 * @property emptyView 空の場合に表示するView
 */
class RecyclerViewEmptyObserver(
    private val recyclerView: RecyclerView,
    private val emptyView: View
) : RecyclerView.AdapterDataObserver() {

    init {
        checkIfEmpty()
    }

    override fun onChanged() {
        checkIfEmpty()
    }

    override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
        checkIfEmpty()
    }

    override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
        checkIfEmpty()
    }

    /**
     * 空かどうかを判定してViewの表示状態を切り替える
     */
    private fun checkIfEmpty() {
        val adapter = recyclerView.adapter
        if (adapter != null) {
            val emptyViewVisible = adapter.itemCount == 0
            emptyView.visibleOrGone(emptyViewVisible)
            recyclerView.goneOrVisible(emptyViewVisible)
        }
    }
}

visibleOrGonegoneOrVisible についてはViewのExentionです。
こちらを参考にしていただければわかると思います。

qoopmk.hatenablog.jp

Step2: RecyclerView Extension を作成

RecyclerViewのExtensionに setEmptyView(view: View) を追加しておきましょう。
こうすることで、ActivityないしFragment側からこれを呼び出すだけで設定が完了します。
注意点としては、これを設定する前に RecyclerView の Adapter は先に設定しておきましょう。

/**
 * 空の状態のViewを設定
 *
 * @param view View
 */
fun RecyclerView.setEmptyView(view: View) {
    val observer = RecyclerViewEmptyObserver(this, view)
    adapter?.registerAdapterDataObserver(observer)
}

結果

以上これだけで、設定が完了です!!

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    recyclerView.adapter = adapter
    recyclerView.setEmptyView(emptyView)
}

補足

通信などの非同期処理を実行して、アイテムを追加する場合はこの setEmptyView(view: View) のタイミングを取得完了時に設定してあげればOKです。

Android エミュレータのギャラリーに画像を追加する

はじめに

Androidエミュレータで画像を選択して送信 とか プロフィール画像を設定する とか、、
実機ではすでに撮影済みの画像を使えばいいのですが、Androidエミュレータだとデフォルトだと画像が何もない状態なので、今回はそんなエミュレータに画像を追加する方法を紹介します。

解決

実はものすごく簡単で、ドラック&ドロップすればいいだけです。

f:id:QoopMk:20191022205238p:plain

Android Viewの表示切り替えをスッキリさせる

はじめに

Android ではご存知の通り、Viewの表示切り替えは ViewのVisibility を切り替えてあげれば変更することができます。

Visibility 状態
View.VISIBLE 表示(デフォルト)
View.INVISIBLE 不可視化(見えなくなるが、サイズは維持される)
View.GONE 非表示(見えなくなる、かつサイズも維持されない)

使いやすく

使いやすくするために以下のような拡張関数を用意してみましょう。

/**
 * VisibleとGoneを切り替える
 *
 * @param isVisible Visibleであるかどうか
 */
fun View.visibleOrGone(isVisible: Boolean) {
    visibility = if (isVisible) View.VISIBLE else View.GONE
}

/**
 * VisibleとInvisibleを切り替える
 *
 * @param isVisible Visibleであるかどうか
 */
fun View.visibleOrInvisible(isVisible: Boolean) {
    visibility = if (isVisible) View.VISIBLE else View.INVISIBLE
}

/**
 * GoneとVisibleを切り替える
 *
 * @param isGone Goneであるかどうか
 */
fun View.goneOrVisible(isGone: Boolean) {
    visibility = if (isGone) View.GONE else View.VISIBLE
}

/**
 * InvisibleとVisibleを切り替える
 *
 * @param isInvisible Invisibleであるかどうか
 */
fun View.invisibleOrVisible(isInvisible: Boolean) {
    visibility = if (isInvisible) View.INVISIBLE else View.VISIBLE
}

/**
 * VISIBLEに切り替える
 */
fun View.toVisible() {
    visibility = View.VISIBLE
}

/**
 * INVISIBLEに切り替える
 */
fun View.toInvisible() {
    visibility = View.INVISIBLE
}

/**
 * GONEに切り替える
 */
fun View.toGone() {
    visibility = View.GONE
}

これらを用意することで以下のように省略できるようになりました!

val view = findByViewId(R.id.view)
if (isViewVisible) {
    view.visibility = View.VISIBLE
} else {
    view.visibility = View.GONE
}

↓

val view = findByViewId(R.id.view)
view.visibleOrGone(isViewVisible)