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)