記録

主にAndroidアプリ開発に関する知見やその周辺に関わることについて記事をかいています

GroupieでViewBindingを簡単に利用する

GroupieのViewHolderから itemView でViewが使えるので、これを利用して ViewBinding.bind(viewHolder.itemView) をしてあげるだけで良い。かなり簡略的だが、実際は次のように使う。

class HogeItem : Item<ViewHolder>() {

    override fun getLayout(): Int = R.layout.item_hoge

    override fun bind(viewHolder: ViewHolder, position: Int) {
        val binding = ItemHogeBinding.bind(viewHolder.itemView)
        binding.textView.text = "Hello world!"
    }
}

また、createViewHolder であらかじめ設定したい場合は、ここでも ViewBinding.bind(itemView) してあげれば良い。

class HogeItem : Item<ViewHolder>() {

    override fun getLayout(): Int = R.layout.item_hoge

    override fun createViewHolder(itemView: View): ViewHolder {
        val binding = ItemHogeBinding.bind(itemView)
        binding.textView.visibility = View.VISIBLE
        return super.createViewHolder(binding.root)
    }
…

ConstraintLayoutのFlowにViewを動的に追加する

追加するViewに View.generateViewId() 等でIDの設定と、ConstraintLayout.LayoutParams を設定する。

val subBinding = ViewSubBinding.inflate(layoutInflater)
subBinding.root.id = View.generateViewId()
subBinding.root.layoutParams = ConstraintLayout.LayoutParams(
        ConstraintLayout.LayoutParams.WRAP_CONTENT,
        ConstraintLayout.LayoutParams.WRAP_CONTENT
)

最後に追加先のViewとFlowに先ほどのViewを追加して終わりです。

val mainBinding = ViewMainBinding.inflate(layoutInflater)
mainBinding.addView(subBinding.root)
mainBinding.flow.addView(subBinding.root)

Androidアプリのマルチモジュールでユニットテストの結果をlintチェック結果と一緒にDangerでコメントする

前回、マルチモジュールでlintチェック結果をまとめてDangerでコメントする方法についての記事をかいた。

m4kvn.hatenablog.com

今回はこれのユニットテスト版もやってみようという話。ユニットテストの結果をDangerでコメントするためには orta/danger-junit というプラグインを利用する。

danger-junitでは、複数のファイルをパースする仕組みがあるので、lintチェック結果をコメントするときみたいにファイルを1つにまとめる必要がない。なので、まずはユニットテスト結果のファイルを集めてくる処理を考える。

ユニットテスト結果のファイルを集める

ユニットテスト結果のファイルは、各モジュールの build/test-results 配下に出力される。例えば、DevelopmentDebugというBuildVariantを指定した./gradlew testDevelopmentDebugUnitTest を実行した場合は、./app/build/test-results/testDevelopmentDebugUnitText/TEST-hogehoge.xml みたいなファイルが出力されている。

これらのファイルを集めてくるためにプロジェクトのルートフォルダで find . | grep "TEST-*" と実行してみる。

❯❯❯❯ find . | grep "TEST-*"
./app/build/test-results/testDevelopmentDebugUnitTest/TEST-hogehoge.xml
./modules/piyo/build/test-results/testDevelopmentDebugUnitTest/TEST-piyopiyo.xml
./modules/makun/build/test-results/testDevelopmentDebugUnitTest/TEST-makunmakun.xml

すると、こんな感じにファイルの相対パスを一覧で取得できる。これをDangerfile内でdanger-junitにわたしparseしてあげれば良さそう。

Dangerfileに設定を記述する

さきほどの出力を .split("\n") してあげるとそれぞれの相対パスとして each でまわせるので、それを利用して junit.parse してあげる処理をDangerfileに記述します。

`find . | grep "TEST-*"`.split("\n").each do |path|
    junit.parse(path)
    junit.report
end

これをandroid_lintの前に実行するよう記述すれば良さそう。

`find . | grep "TEST-*"`.split("\n").each do |path|
    junit.parse(path)
    junit.report
end

android_lint.skip_gradle_task = true
android_lint.filtering = false
android_lint.report_file = "build-reports/lint-results.xml"
android_lint.lint(inline_mode: true)

Bitriseでの実行結果

前回と同じようにBitrise上でこれを動かしてみる。

f:id:m4kvn:20190729164208p:plain

するとこんな感じにandroid_lintと一緒にコメントしてくれる。

Androidアプリのマルチモジュールでlintチェック結果をまとめてDangerでコメントする

Androidアプリ開発でマルチモジュールによる開発をすすめていた場合、Lintを実行すると各モジュールのbuildフォルダ配下にlintチェック結果のファイルが生成されます。もの挙動自体は正しくまったく問題はないのですが、GitHubを利用していてプルリクにDanger等でlintチェックの結果をコメントしていた場合、アプリケーションモジュールの結果のみがコメントされたり、特定のモジュールの結果のみがコメントされてしまいます。多くの場合、マルチモジュール開発でのモジュールは同じリポジトリ内に配置されていると思うので、これはなんとかして解決したいです。

解決の選択肢

この問題を解決する選択肢はいくつかある。

  1. モジュール毎にGitHubリポジトリを作成し、それぞれCIを設定する
  2. モジュールの数だけDangerを実行する
  3. lintチェック結果をまとめて一度だけDangerを実行する

選択肢1

モジュール毎にGitHubリポジトリを作成することで、そもそもプルリクがモジュール毎に行われるため、問題が発生しないという考え方です。ただ、自分の開発している環境ではモジュールが20近くあるのでこれは現実的じゃないかなと。また、Androidアプリ開発のマルチモジュールでモジュール毎にリポジトリを切る運用はあまりみたことがない。なぜなら、各モジュールがコアなモジュールに依存していたりとモジュール間の依存が結構発生するのと、リファクタリング時に他のモジュールのリファクタリングが走らなくなるので、モジュール毎にちゃんとライブラリ的な管理が必要になるからとかいろいろある。とりあえずこの選択肢はなし。

選択肢2

モジュールの数だけDangerを実行する場合は、それぞれDangerfileを作成して結果のXMLファイルそれぞれ指定してあげる必要があります。これはモジュールが増えるたび、Dangerfileの作成とCIの設定が必要になるのでやりたくない。また、ひとつのプルリクに対して複数のDangerを実行するとコメントが最後の1回で上書きされるか、ちゃんと設定したとしても実行した回数分コメントがされるので、見た目的にもちょっと嫌だ。GitHubとSlackを連携していたりすると大量に通知されることになったりもする。この選択もなし。

選択肢3

linkチェックで生成される結果のファイルを1つのファイルとしてまとめ、1度だけDangerを実行する。これは、マルチモジュールじゃなかった場合のDangerの利用と変わらない結果になるため理想。やりたいことがやれる感じがするので、この選択肢の実現を目指す。

対応

lintの設定

lintチェック結果のXMLファイルが、指定した場所に、指定した名前、で生成されるよう build.gradle で設定しておきます。この設定は、モジュール単位で設定してあげる必要がある ので、androidブロックを共通化させているファイル等で設定してあげると良さそうです。

android {
    lintOptions {
        xmlReport true
        xmlOutput rootProject.file("./build-reports/lint-results-${project.getDisplayName()}.xml")
    }
}

書き出されるファイル名がモジュール毎に変わるようにすることで、結果のファイルが上書きされるのを防いでいます。今回の例でモジュールが :modules:feature:home という階層にあった場合、lint-results-project ':modules:feature:home'.xml という名前でファイルが書き出されます。

この設定で ./gradlew lint 等でlintのGradleタスクを実行してあげると結果のファイルが ./build-reports 配下に生成されます。

lintチェック結果のXMLファイルをマージする

android_lintで結果をプルリクにコメントするために、結果のXMLファイルをマージして1つのXMLファイルを作ります。結果のXMLファイルは以下のような中身になっています。

<?xml version="1.0" encoding="UTF-8"?>
<issues format="5" by="lint 3.4.2">

    <issue
        id="…"
        severity="Warning"
        message="…"
        category="Lint"
        priority="…"
        summary="…"
        explanation="…"></issue>

</issues>

なので全ての <issue><issues> にまとめてあげれば良さそう。ということで以下のようなRubyスクリプトを作成した。

require 'nokogiri'

Dir.chdir('build-reports')

new_doc = nil

Dir::glob('lint-results-*.xml') do |item|
  file = File.new(item)

  if new_doc.nil?
    new_doc = Nokogiri.XML(file)
  else
    doc = Nokogiri.XML(file)
    issues = doc.search('issue')
    new_doc.at('issues').add_child(issues)
  end
end

File.open('lint-results.xml', 'w') do |file|
  unless new_doc.nil?
    file.puts(new_doc.to_xml(indent: 4))
  end
end

1つ目に読み込んだファイルをベースに、他のファイルの <issue><issues> にまとめて、lint-results.xml というファイルで出力します。実行にはnoogiriが必要なので予め gem install nokogiri しておきます。これをプロジェクトのrootディレクトリで実行すると結果が1つにマージされたXMLファイルがbuild-reportsに作成されています。

Dangerでファイルを指定する

あとは Dangerfile で生成されたXMLファイルを指定してあげるだけ。

android_lint.skip_gradle_task = true
android_lint.filtering = false
android_lint.report_file = "build-reports/lint-results.xml"
android_lint.lint(inline_mode: true)

ここまでの一連の流れをCIで実行してあげる。

Bitriseのワークフローを設定する

Bitriseでは以下の流れのワークフローを作成します。

  1. Gradle Taskのlintを実行し
  2. XMLをマージするRubyスクリプトを実行
  3. Dangerを実行するRubyスクリプトを実行

f:id:m4kvn:20190728020543p:plain

ちなみに、Rubyスクリプトを実行するためには bitrise-steplib/steps-ruby-script を選択しています。理由はGemfileのサポートがあり、Bitriseで直接Rubyをかけるからです。Bitriseの設定を変えてビルドを実行するために毎回コミットをするのがつらくて一旦こういう形にしています。安定したらスクリプトファイルをGitの管理化に配置する予定です。Rubyスクリプトの設定は以下のようになっています。

f:id:m4kvn:20190728020956p:plain

f:id:m4kvn:20190728020808p:plain

また、忘れずに DANGER_GITHUB_API_TOKEN をSecretsに設定しておきます。

f:id:m4kvn:20190728023116p:plain

あとはTriggersのPULL REQUESTに作成したワークフローを設定しておきます。

danger-android_lintの実行結果

プルリクエスト時にBitrise上でDangerが実行されると以下のようにlintチェック結果をコメントしてくれます。

f:id:m4kvn:20190728023507p:plain