DEV Community

Rodrigo Sicarelli
Rodrigo Sicarelli

Posted on • Edited on

Android Plataforma - Parte 14: Aderindo a funcionalidades experimentais do compilador do Kotlin

🌱 Branch: 14/opt-in-experimental-kotlin-compiler

🔗 Repositório: github.com/rsicarelli/kotlin-gradle-android-platform

⬅️ Artigo Anterior: Parte 13: Incluindo módulos puro JVM

➡️ Próximo Artigo: Parte 15: Cuidando do código com Detekt, Klint e Spotless


No último artigo, extendemos nossa plataforma com a capacidade de declarar módulos JVM.

Neste artigo, iremos além e configurar opções de compilação para permitir que cada módulo "adira" a funcionalidades experimentais.


Opt-In no Kotlin

Uma das práticas adotadas por times ao projetar uma API de forma segura é o uso do sistema de "opt-in" para funcionalidades ou APIs específicas.

Anotação RequiresOptIn

A anotação RequiresOptIn indica que uma classe de anotação é um marcador para uma API que exige um opt-in explícito.

Quando se depara com uma API anotada com um marcador que também está anotado com RequiresOptIn, o compilador nos força a concordar explicitamente em usar essa 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,
    }
}
Enter fullscreen mode Exit fullscreen mode

Contagiosidade

APIs anotadas com marcadores que requerem opt-in são "contagiosas". Qualquer uso ou menção a essa API em outras declarações também demandará um opt-in.

Por exemplo:

@UnstableApi
class Unstable

@OptIn(UnstableApi::class)
fun foo(): Unstable = Unstable()
Enter fullscreen mode Exit fullscreen mode

Ao tentar usar a função foo, seremos alertados sobre a necessidade de optar pela API instável.

Anotação OptIn

A anotação OptIn nos permite declarar que estamos cientes e aceitamos os riscos associados ao uso de uma API marcada.

@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>
)
Enter fullscreen mode Exit fullscreen mode

Utilizando APIs experimentais

Para ilustrar tudo o que discutimos, vamos usar um componente do Material3 que está anotado com RequiresOptIn:

import androidx.compose.material3.Card
..

@Composable
fun HomeScreen() {
    Surface(
        modifier = Modifier.fillMaxSize(),
        color = MaterialTheme.colorScheme.background
    ) {
        //IDE vai dar um erro/alerta nessa linha
        Card(
            modifier = Modifier
                .fillMaxWidth()
                .wrapContentHeight()
                .padding(all = 16.dp),
            onClick = { },
            content = {
                DetailsScreen()
            }
        )
    }
}
Enter fullscreen mode Exit fullscreen mode

Note o erro/alerta que surge na tela:

Image description

Para resolver esse erro, simplesmente adicionamos o OptIn em nosso compose:

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun HomeScreen() {
   ..
}
Enter fullscreen mode Exit fullscreen mode

Para situações específicas, essa abordagem funciona. Mas considere funções que são usadas frequentemente, como Flow.flatMapConcat:

@OptIn(FlowPreview::class) 
fun main() {
    flowOf(null)
        .flatMapConcat { flowOf(true) }
}
Enter fullscreen mode Exit fullscreen mode

Repetir essa declaração em cada uso pode ser tedioso, especialmente em codebases extensos.

Personalizando nossa compilação Kotlin para evitar a necessidade de OptIn

A boa notícia é que podemos configurar nosso applyKotlinOptions() para dar opt-in nas features necessárias.

Image description

1 - Atualizaremos nosso modelo CompilationOptions para aceitar uma lista de 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()
}
Enter fullscreen mode Exit fullscreen mode

2 - Vá até a função fun applyKotlinOptions() e atualize o uso:

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

3 - Sincronize o projeto. Em seguida, vá ao módulo que está usando essas features e utilize a nova 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 {
    ..
}
Enter fullscreen mode Exit fullscreen mode

Sucesso!

Agora, podemos usar as funcionalidades experimentais de Coroutines e Material3 sem a necessidade de adotar a anotação OptIn.

No próximo artigo, focaremos na qualidade de código, introduzindo recursos de análise estática com Detekt e Spotless para auxiliar na autoformatação, aderindo ao estilo de código do projeto (.editorconfig).

Top comments (0)