たかぎとねこの忘備録

プログラミングに関する忘備録を自分用に残しときます。マサカリ怖い。

AndroidアプリでJetpack Composeを使って使用しているライブラリのライセンスを表示してみる

Androidアプリを開発していると直面するライセンス表示問題。

色々調べるとCookpadさんが公開しているプラグインにたどり着いた。だが、お察しのとおりすでにアーカイブされている。

github.com

代わりにおすすめされているのが、Googleが公開しているOSS Licenses Gradle Plugin

play-services-plugins/oss-licenses-plugin at master · google/play-services-plugins · GitHub

残念ながらJetpack Composeには現時点で対応していない。僕はJetpack Composeを使ってライセンスを表示したい。

そこで、見つけたのがJumpei Matsudaさんが公開しているlicense-list-plugin

github.com

このプラグインを使ってartifact-definition.ymlを生成し、そのデータをパースした内容をCompose可能な関数で表示してみる。

アプリのbuild.gradleにて、プラグインの設定を行う。

plugins {
    id 'com.android.application'
    id "io.github.jmatsu.license-list"
}

// Licensing
apply plugin: "com.android.application"
apply plugin: "io.github.jmatsu.license-list"

ルートディレクトリにあるプロジェクトのbuild.gradleにbuildscriptの設定を追加する。

buildscript {
    ext {
        license_list_gradle_version = '0.8.1'
    }
    dependencies {
        classpath "io.github.jmatsu:license-list-gradle:$license_list_gradle_version"
    }
}

追加が完了したら、実際にプラグインを実行する。

./gradlew initLicenseList

実行後、app/artifact-definition.ymlapp/license-catalog.ymlが作成されているはず。

モジュールを右クリックして、New -> Folder -> Assets Folderの順番で選択していき、assetsフォルダを作成する。 そのassetsの中に先ほど生成されたartifact-definition.ymllicense-catalog.ymlを移動する。

artifact-definition.ymlの中身をパースするために、kamlライブラリを導入する。

github.com

build.gradleに次の設定と依存関係を追加する。

plugins {
    id 'org.jetbrains.kotlin.plugin.serialization' version '1.4.20'
}

dependencies {
    implementation "com.charleskorn.kaml:kaml:0.49.0" // Get the latest version number from https://github.com/charleskorn/kaml/releases/latest
}

assets/artifact-definition.ymlを実際に読み込むために、Contextインスタンスを使用する。

@kotlinx.serialization.Serializable
data class License(
    val release: Map<String, List<ArtifactDefinition>>,
    val test: Map<String, List<ArtifactDefinition>>,
    val androidTest: Map<String, List<ArtifactDefinition>>,
)

@kotlinx.serialization.Serializable
data class ArtifactDefinition(
    val key: String,
    val displayName: String,
    val url: String?,
    val copyrightHolders: List<String>,
    val licenses: List<String>
)

fun parseLicense(context: Context) {
    val yamlStr = context.assets.open("artifact-definition.yml").bufferedReader().use {
        it.readText()
    }
    val result = Yaml.default.decodeFromString(License.serializer(), yamlStr)
}

ひとつのライセンス要素を表示するためのCompose可能な関数を定義する。

@Composable
fun LicenseItem(
    displayName: String,
    url: String?,
    copyrightHolders: List<String>,
    licenses: List<String>,
) {
    Column() {
        Text(
            text = displayName,
            style = MaterialTheme.typography.titleLarge
        )
        Spacer(modifier = Modifier.height(8.dp))

        if (url != null) {
            Text(
                text = url ?: ""
            )
            Spacer(modifier = Modifier.height(8.dp))
        }

        if (copyrightHolders.isNotEmpty()) {
            copyrightHolders.forEach { copyrightHolder ->
                Text(
                    text = copyrightHolder
                )
            }

            Spacer(modifier = Modifier.height(8.dp))
        }

        licenses.forEach { license ->
            Text(
                text = license
            )
        }
    }
}

そして、先ほど読み取ったデータを使い、LazyColumnを呼び出してリスト形式で表示する。

LazyColumn(
    modifier = Modifier
        .fillMaxWidth()
        .fillMaxHeight()
) {
    if (license != null) {
        items(license.release.keys.toList()) { groupKey ->
            val group = license.release[groupKey]
            group?.forEach { groupItem ->
                LicenseItem(
                    displayName = groupItem.displayName,
                    url = groupItem.url,
                    copyrightHolders = groupItem.copyrightHolders,
                    licenses = groupItem.licenses
                )

                Spacer(modifier = Modifier.height(24.dp))
            }
        }

        items(license.test.keys.toList()) { groupKey ->
            val group = license.release[groupKey]
            group?.forEach { groupItem ->
                LicenseItem(
                    displayName = groupItem.displayName,
                    url = groupItem.url,
                    copyrightHolders = groupItem.copyrightHolders,
                    licenses = groupItem.licenses
                )

                Spacer(modifier = Modifier.height(24.dp))
            }
        }

        items(license.androidTest.keys.toList()) { groupKey ->
            val group = license.release[groupKey]
            group?.forEach { groupItem ->
                LicenseItem(
                    displayName = groupItem.displayName,
                    url = groupItem.url,
                    copyrightHolders = groupItem.copyrightHolders,
                    licenses = groupItem.licenses
                )

                Spacer(modifier = Modifier.height(24.dp))
            }
        }
    }
}

実際の画面。

参考

Read a Text asset(text file from assets folder) as a String in Kotlin (Android) - Stack Overflow