1
0

More than 3 years have passed since last update.

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

Last updated at Posted at 2021-08-13

環境

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

発生条件とエラー内容

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

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 を取得できるようになっています。

  @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 に置き換わればあるいは?
  • Jetpack Compose が普及すれば DataBinding kapt に頼る必要はなくなる、と期待するのがベストかもしれません
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0