Androidの自作ライブラリの処理実行時にNoClassDefFoundErrorが起きる

## 概要

- 自作ライブラリの処理の実行時に、NoClassDefFoundErrorが起きる
- 直接の原因は、自作ライブラリをMavenリポジトリにデプロイする際に、自作ライブラリが依存しているライブラリの情報が欠落すること
- 依存ライブラリの情報が欠落するのは、Maven Publishプラグインの設定が間違っているから
- Android公式のMaven Publishプラグインの設定にしたら問題は解決した
  - https://developer.android.com/studio/build/maven-publish-plugin?hl=ja
- 特に理由がないなら、公式の設定におとなしく従っておきましょう

## 前提

アプリX、自作ライブラリY、外部ライブラリZがある。
アプリX -> 自作ライブラリY -> 外部ライブラリZの方向に依存関係がある。
自作ライブラリYも外部ライブラリZもMavenリポジトリにホストしている。


## 遭遇したこと

アプリXのビルドは通るが、自作ライブラリY内の外部ライブラリZを使う処理の実行時に、NoClassDefFoundErrorが発生する。

**Errorログの例**

```shell
E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.app, PID: 31546
    java.lang.NoClassDefFoundError: Failed resolution of: Landroidx/activity/result/contract/ActivityResultContracts$StartActivityForResult;
        at com.exmaple.app.MainActivity.<init>(MainActivity.kt:<73>)
        ...
```

## 原因

### 直接の原因：ライブラリの依存関係の情報の欠落

アプリXの依存ライブラリとして、外部ライブラリZを記述すると、この問題は起きなくなった。
問題は起きなくなったので解散!、と言いたいところだが、謎が残った。

Android Xなどの普通のライブラリでは、このような問題が起きないということだ。
たとえば、"recyclerview"というライブラリは、"androidx.core:core-ktx"というライブラリに依存している[^1]。
ただ、アプリの依存関係には"recyclerview"を書くだけで、実行時にNoClassDefFoundErrorは起きない。
これは、"recyclerview"が"androidx.core:core-ktx"に依存しているということが、アプリにも伝わっているということだ。

よって、自作ライブラリYが外部ライブラリZに依存していることがアプリに伝わっていないことが問題の原因であることが分かる。
実際、自作ライブラリYのpomファイルとrecyclerviewのpomファイルを見ると、前者には<dependencies>の情報がない[^2]。

**自作ライブラリYのpom**

```xml
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example</groupId>
  <artifactId>libraryY</artifactId>
  <version>1.0.0</version>
  <packaging>aar</packaging>
</project>
```

**Android Xのrecyclerviewのpomの抜粋**

```xml
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <groupId>androidx.recyclerview</groupId>
  <artifactId>recyclerview</artifactId>
  <version>1.1.0</version>
  <packaging>aar</packaging>
  ...
  <dependencies>
    <dependency>
      <groupId>androidx.core</groupId>
      <artifactId>core</artifactId>
      <version>1.1.0</version>
      <type>aar</type>
      <scope>compile</scope>
    </dependency>
    ...
  </dependencies>
</project>
```

### 自作ライブラリから依存ライブラリの情報が欠落する原因

単刀直入に言って、Maven Publishプラグインの設定に問題がある。

**問題解決前の自作ライブラリYのMaven Publishプラグインの設定**

```gradle
apply plugin: 'maven-publish'
...
publishing {
    publications {
        release(MavenPublication) {
            groupId 'com.example'
            artifactId 'libraryY'
            version '1.0.0'
            artifact source: file("${project.buildDir}/outputs/aar/${project.name}-release.aar")
        }
    }
    repositories {
        maven {
            url "<maven repoisitory url>"
        }
    }
}
```

artifact sourceにaarを指定しているのが問題で。aarにはライブラリの依存関係の情報は含まれない。


## 問題の対処方法

ライブラリの依存関係が書いてあるのはgradleのファイルなので、gradleのファイルに記述した依存情報をpomに出力する必要がある。こう書くと面倒そうだが、[Android公式ページの設定](https://developer.android.com/studio/build/maven-publish-plugin?hl=ja)に従うと、gradleからpomへの依存関係の転写は勝手にやってくれる。問題解決後の自作ライブラリYのMaven Publishのプラグインの設定を示す。

**問題解決後の自作ライブラリYのMaven Publishプラグインの設定**

```gradle
apply plugin: 'maven-publish'
...
afterEvaluate {
    publishing {
        publications {
            release(MavenPublication) {
                from components.release // この記述が重要
                groupId 'com.example'
                artifactId 'libraryY'
                version '1.0.0'
            }
        }
        repositories {
            maven {
                url "<maven repoisitory url>"
            }
        }
    }
}
```

"from components.release"の記述の記述によって、BuildVariantが"Release"のGradleのビルド設定から、aarをビルドしたり、ライブラリの依存情報をpomの<dependencies>に出力してくれる。

それにより、アプリXに外部ライブラリZの依存関係を記述しなくても、実行時にNoClassDefFoundErrorは発生しなくなる。

## 最後に

「特に理由がないなら、Android公式の設定に従うのが楽」ということだ。



[^1]: https://github.com/androidx/androidx/blob/androidx-master-dev/cardview/cardview/build.gradle
[^2]: pomファイルは、"~/.gradle"配下を探すと見つかる。Mavenプロジェクトの情報が書かれたファイル。詳細は、http://maven.apache.org/guides/introduction/introduction-to-the-pom.html。
[^3]: https://developer.android.com/studio/build/maven-publish-plugin?hl=ja