🌱 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)