Android プラットフォーム - 第14部: Kotlin コンパイラの実験的機能に積極的に参加する

🌱 ブランチ: 14/opt-in-experimental-kotlin-compiler

🔗 リポジトリ: github.com/rsicarelli/kotlin-gradle-android-platform

⬅️ 前の記事: 第13部: 純粋なJVMモジュールを組み込む

➡️ 次の記事: 第15部: Detekt、Klint、Spotlessでコードをケアする


前回の記事では、JVMモジュールを宣言する能力をプラットフォームに追加しました。

この記事ではさらに一歩進んで、各モジュールが実験的な機能に「参加する」ためのコンパイルオプションを設定します。


KotlinでのOpt-In

APIを安全に設計するためにチームが採用する実践の一つに、特定の機能やAPIに対する「opt-in」制度の使用があります。

RequiresOptIn アノテーション

RequiresOptInアノテーションは、明示的なopt-inを要求するAPIのためのマーカーである注釈クラスを示します。

@Target(ANNOTATION_CLASS)
@Retention(BINARY)
@SinceKotlin("1.3")
public annotation class RequiresOptIn(
    val message: String = "",
    val level: Level = Level.ERROR
) {
    public enum class Level {
        WARNING,
        ERROR,
    }
}

Contagiosidade

opt-inを要求するマーカーで注釈されたAPIは「感染性」があります。そのようなAPIを使ったり言及したりする他の宣言でもopt-inが求められます。

例えば:

@UnstableApi
class Unstable

@OptIn(UnstableApi::class)
fun foo(): Unstable = Unstable()

foo関数を使用しようとすると、APIの不安定性を受け入れる必要があると警告されます。

OptIn アノテーション

OptInアノテーションにより、マークされたAPIの使用に関連するリスクを認識して受け入れることを宣言できます。

@Target(
    CLASS, PROPERTY, LOCAL_VARIABLE, VALUE_PARAMETER, CONSTRUCTOR, FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER, EXPRESSION, FILE, TYPEALIAS
)
@Retention(SOURCE)
@SinceKotlin("1.3")
public annotation class OptIn(
    vararg val markerClass: KClass<out Annotation>
)

実験的APIの使用

議論したことを具体例で見てみましょう。Material3のコンポーネントでRequiresOptInで注釈されたものを使用します:

import androidx.compose.material3.Card
..

@Composable
fun HomeScreen() {
    Surface(
        modifier = Modifier.fillMaxSize(),
        color = MaterialTheme.colorScheme.background
    ) {
        //この行でIDEはエラー/警告を出します
        Card(
            modifier = Modifier
                .fillMaxWidth()
                .wrapContentHeight()
                .padding(all = 16.dp),
            onClick = { },
            content = {
                DetailsScreen()
            }
        )
    }
}

画面に表示されるエラー/警告に注意してください:

画像説明

このエラーを解決するために、私たちは OptIn を Composable 式に追加します:

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun HomeScreen() {
   ..
}

特定の状況にはこのアプローチが機能しますが、Flow.flatMapConcatのように頻繁に使用される関数については、各使用ごとにこの宣言を繰り返すのは手間です。

Kotlin コンパイルをパーソナライズして OptIn の必要性を避ける

applyKotlinOptions()を構成して必要な機能でopt-inを行なうことができます。

1 - CompilationOptions モデルを更新して FeatureOptIn リストを受け入れるようにします:

data class CompilationOptions(
    ..
    val featureOptIns: List<FeatureOptIn>,
) {

    val extraFreeCompilerArgs: List<String>
        get() = featureOptIns.map { "-opt-in=${it.flag}" }

    enum class FeatureOptIn(val flag: String) {
        ExperimentalMaterial3("androidx.compose.material3.ExperimentalMaterial3Api"),
        ExperimentalCoroutinesApi(flag = "kotlinx.coroutines.ExperimentalCoroutinesApi"),
    }
}

class CompilationOptionsBuilder {

    ..
    private val featureOptInsBuilder = FeatureOptInBuilder()

    fun optIn(vararg optIn: FeatureOptIn) {
        featureOptInsBuilder.apply {
            featureOptIns = optIn.toList()
        }
    }

    internal fun build(): CompilationOptions = CompilationOptions(
        ..
        featureOptIns = featureOptInsBuilder.build()
    )
}

class FeatureOptInBuilder {

    var featureOptIns: List<FeatureOptIn> = mutableListOf()

    internal fun build(): List<FeatureOptIn> = featureOptIns.toList()
}

2 - fun applyKotlinOptions() 関数に移動して、使い方を更新します:

internal fun Project.applyKotlinOptions(compilationOptions: CompilationOptions) {
    tasks.withType<KotlinCompile>().configureEach {
        kotlinOptions {
            allWarningsAsErrors = compilationOptions.allWarningsAsErrors
            jvmTarget = compilationOptions.jvmTarget
            compilerOptions.freeCompilerArgs.addAll(compilationOptions.extraFreeCompilerArgs)
        }
    }
}

3 - プロジェクトを同期します。次に、これらの機能を使用しているモジュールで新しいDSLを使用します:

import com.rsicarelli.kplatform.androidLibrary
import com.rsicarelli.kplatform.options.CompilationOptions.FeatureOptIn.ExperimentalCoroutinesApi
import com.rsicarelli.kplatform.options.CompilationOptions.FeatureOptIn.ExperimentalMaterial3

plugins {
    id(libs.plugins.android.library.get().pluginId)
    kotlin("android")
}

androidLibrary(
    compilationOptionsBuilder = {
        optIn(ExperimentalCoroutinesApi, ExperimentalMaterial3)
    }
)

dependencies {
    ..
}

成功!

これで、CoroutinesとMaterial3の実験的な機能をOptInアノテーションを使わずに使用できます。

次の記事では、DetektSpotlessを利用して静的解析を導入し、自動フォーマットとプロジェクトのコードスタイル(.editorconfig)への準拠を進めることに重点を置きます。

こちらの記事はdev.toの良い記事を日本人向けに翻訳しています。
https://dev.to/rsicarelli/android-plataforma-parte-14-aderindo-a-funcionalidades-experimentais-do-compilador-do-kotlin-3b0g