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でコメントする方法についての記事をかいた。
今回はこれのユニットテスト版もやってみようという話。ユニットテストの結果を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上でこれを動かしてみる。
するとこんな感じにandroid_lintと一緒にコメントしてくれる。
Androidアプリのマルチモジュールでlintチェック結果をまとめてDangerでコメントする
Androidアプリ開発でマルチモジュールによる開発をすすめていた場合、Lintを実行すると各モジュールのbuildフォルダ配下にlintチェック結果のファイルが生成されます。もの挙動自体は正しくまったく問題はないのですが、GitHubを利用していてプルリクにDanger等でlintチェックの結果をコメントしていた場合、アプリケーションモジュールの結果のみがコメントされたり、特定のモジュールの結果のみがコメントされてしまいます。多くの場合、マルチモジュール開発でのモジュールは同じリポジトリ内に配置されていると思うので、これはなんとかして解決したいです。
解決の選択肢
この問題を解決する選択肢はいくつかある。
選択肢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では以下の流れのワークフローを作成します。
ちなみに、Rubyスクリプトを実行するためには bitrise-steplib/steps-ruby-script を選択しています。理由はGemfileのサポートがあり、Bitriseで直接Rubyをかけるからです。Bitriseの設定を変えてビルドを実行するために毎回コミットをするのがつらくて一旦こういう形にしています。安定したらスクリプトファイルをGitの管理化に配置する予定です。Rubyスクリプトの設定は以下のようになっています。
また、忘れずに DANGER_GITHUB_API_TOKEN
をSecretsに設定しておきます。
あとはTriggersのPULL REQUESTに作成したワークフローを設定しておきます。
danger-android_lintの実行結果
プルリクエスト時にBitrise上でDangerが実行されると以下のようにlintチェック結果をコメントしてくれます。