Android: マルチモジュール構成の DataBinding で  View is not a binding layout. エラーが発生する

# 環境

* OpenJDK 11
* Gradle 7.1.1
* Android Gradle Plugin 4.2 ~ (7.0.0, 7.1.0-alpha08 でも再現確認)
* Gradle Multimodule 構成

# 発生条件とエラー内容

Android Studio や CI でビルドしたアプリが、特定の画面へ遷移したときに以下のエラー(クラッシュ)が発生することがある。

* [AppCompatActivity(contentLayoutId: Int)](https://developer.android.com/reference/kotlin/androidx/appcompat/app/AppCompatActivity#appcompatactivity_2) または [Fragment(contentLayoutId: Int)](https://developer.android.com/reference/kotlin/androidx/fragment/app/Fragment#fragment_2) を使ってレイアウトを自動生成したあとに [DataBindingUtil.bind(root: View)](https://developer.android.com/reference/android/databinding/DataBindingUtil?hl=en#bind) で DataBinding を取得する実装をしている
* モジュール数が多いと発生しやすい
* `./gradlew clean :app:kaptReleaseKotlin --no-build-cache` のように DataBinding のキャッシュが存在しない状態だと再現しやすい

```
Caused by: java.lang.IllegalArgumentException: View is not a binding layout. Tag: layout/activity_main_0
```

```
2021-08-13 18:44:50.658 21905-21905/? E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.app, PID: 21905
    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.app/com.example.app.MainActivity}: java.lang.IllegalArgumentException: View is not a binding layout. Tag: layout/activity_main_0
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2956)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3091)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1821)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:280)
        at android.app.ActivityThread.main(ActivityThread.java:6744)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
     Caused by: java.lang.IllegalArgumentException: View is not a binding layout. Tag: layout/activity_main_0
        at androidx.databinding.DataBindingUtil.bind(DataBindingUtil.java:185)
        at androidx.databinding.DataBindingUtil.bind(DataBindingUtil.java:152)
        at com.wada811.databinding.ActivityDataBinding$dataBinding$1.bind(ActivityDataBinding.kt:26)
        at com.wada811.databinding.ActivityDataBinding$dataBinding$1.getValue(ActivityDataBinding.kt:15)
        at com.wada811.databinding.ActivityDataBinding$dataBinding$1.getValue(ActivityDataBinding.kt:11)
        at com.example.app.MainActivity.getBinding(MainActivity.kt:44)
        at com.example.app.MainActivity.onCreate(MainActivity.kt:170)
        at android.app.Activity.performCreate(Activity.java:7136)
        at android.app.Activity.performCreate(Activity.java:7127)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1272)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2936)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3091) 
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78) 
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108) 
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1821) 
        at android.os.Handler.dispatchMessage(Handler.java:106) 
        at android.os.Looper.loop(Looper.java:280) 
        at android.app.ActivityThread.main(ActivityThread.java:6744) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858) 
```

# 原因

DataBinding は `kapt{variant}Kotlin` タスクで `{module}/build/generated/source/kapt/{variant}/{package}.../DataBinderMapperImpl.java` を生成します。

DataBinderMapperImpl には以下のような collectDependencies() が生成されており、そのモジュールが依存しているサブモジュールの DataBinderMapperImpl を取得できるようになっています。

```java
  @Override
  public List<DataBinderMapper> collectDependencies() {
    ArrayList<DataBinderMapper> result = new ArrayList<DataBinderMapper>(10);
    result.add(new androidx.databinding.library.baseAdapters.DataBinderMapperImpl());
    result.add(new com.example.app.feature.feature1.DataBinderMapperImpl());
    result.add(new com.example.app.feature.feature2.DataBinderMapperImpl());
...
    return result;
  }
```

ところが、DataBinding の `kapt{variant}Kotlin` タスクは依存しているサブモジュールの `:{module}:kapt{variant}Kotlin` タスクの完了を待っておらず、サブモジュールの DataBinderMapperImpl を認識できないまま DataBinderMapperImpl.collectDependencies() を生成してしまうことがあるようです。

不完全な DataBinderMapperImpl であってもアプリのビルドは成功します。該当の画面で DataBindingUtil.bind() を実行すると、取りこぼされたモジュールの DataBinderMapperImpl を見つけることができずにエラーとなります。

すべてのサブモジュールの DataBinderMapperImpl が生成されたあとであれば、親モジュールの DataBinderMapperImpl は正しく生成されるため、ローカルの開発環境などで何度もビルドしている環境ではこの問題は発生しづらいようです。

# 対策

この問題が修正されるまでは以下のワークアラウンドが有効です。

* CI 環境で、リリースビルド前に `./gradlew kapt{variant}Kotlin` を実行しておく

例:

```
# すべてのサブモジュールの kapt{variant}Kotlin を実行
# モジュールごとに {variant} が異なる場合は Gradle スクリプトなどで工夫しても良いかも
./gradlew kaptReleaseKotlin
./gradlew bundleRelease
```

各モジュールに生成された DataBinderMapperImpl.java を眺めて、collectDependencies() が漏れなく実装されていることが確認できれば解決です。

# その他

* 厳密にはモジュールの依存関係を逆順に辿って一つずつ kapt タスクを実行していく必要があるかもしれませんが、現実にはそこまで階層の深いマルチモジュールは想定しづらいため、すべてのサブモジュールの kapt タスクを1回実行できればよしとしています
* `./gradlew kaptReleaseKotlin bundleRelease` のように gradlew コマンドをまとめるとうまくいきませんでした
* `./gradlew bundleRelease --no-parallel` のように並列実行を無効としても問題は解消されませんでした
* gradle.properties `kapt.use.worker.api=false` を設定したところ、Android Studio からのビルドでは問題が解消しているように見えましたが、CI (Github Actions) では引き続き問題が発生しているようでした
* DataBinding kapt プラグイン側で修正して欲しいところですが、DataBinding と kapt キャッシュは今までもトラブルが多かったので、今後も DataBinding kapt プラグインに期待することは難しいかもしれません。DataBinding が [ksp](https://github.com/google/ksp) に置き換わればあるいは？
* Jetpack Compose が普及すれば DataBinding kapt に頼る必要はなくなる、と期待するのがベストかもしれません