🌱 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, } }
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()
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> )
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() } ) } }
Note o erro/alerta que surge na tela:
Para resolver esse erro, simplesmente adicionamos o OptIn
em nosso compose:
@OptIn(ExperimentalMaterial3Api::class) @Composable fun HomeScreen() { .. }
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) } }
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.
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() }
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) } } }
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 { .. }
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)