Reputation: 2837
I have an app that makes heavy use of experimental features for Jetpack Compose so I have to declare a bunch of annotations on the composables. Since these annotations require callers to also declare them I have ended up in a situation where I have an activity with the following code:
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.ui.ExperimentalComposeUiApi
import com.google.accompanist.navigation.material.ExperimentalMaterialNavigationApi
import com.google.accompanist.pager.ExperimentalPagerApi
import com.google.accompanist.permissions.ExperimentalPermissionsApi
…
class MainActivity : AppCompatActivity() {
@ExperimentalPermissionsApi
@ExperimentalComposeUiApi
@ExperimentalPagerApi
@ExperimentalMaterialNavigationApi
@ExperimentalMaterialApi
override fun onCreate(savedInstanceState: Bundle?) {
// … wiring up compose code (which propagates the experimental annotations)
An alternative to avoid this situation would be to use the @OptIn
instead but since only one is allowed per declaration it doesn't work out for my case with multiple experimental features.
Any way… This works fine — In Kotlin 1.5.
With Kotlin 1.6 I am getting a compilation error:
Opt-in requirement marker annotation on override requires the same marker on base declaration
But the base declaration is in the standard API that I cannot change. How can I make this compile (and work as before)?
Upvotes: 21
Views: 12381
Reputation: 1060
@ExperimentalAnimationApi
❌
This annotation means - "This is an experimental API, all users have to explicitly opt in to use". Rarely the case for application developers.
@OptIn(ExperimetalAnimationApi::class)
✅
This annotation means - "I am opting in to use an experimental api". It does not force the users of this method/class to add annotation in their code.
@ExperimentalAnimationApi
@Composable
fun MyCode()
means MyCode
is experimental animation api, if you want to use MyCode
, explicitly opt in to ExperimentalAnimationApi
@Composable
fun MyOtherCode() {
MyCode() // ERROR!, doesn't compile and shows red underlines in IDE
}
👆 is what typically causes too many @ExperimentalAnimationApi
annotations in our code. DO NOT DO THIS
@OptIn(ExperimentalAnimationApi::class)
@Composable
fun MyCode() {
// experimental animation api usage
}
👆 will NOT force the callers to add any annotations
@Composable
fun MyOtherCode() {
MyCode() // safe, compiles and doesn't show any errors in IDE
}
As app developers, we almost always have to
use @OptIn(ExperimentalAnimationApi::class)
and
NOT @ExperimentalAnimationApi
,
unless our code itself is exposing experimental declarations in it's public surface as return types or function arguments etc.
Alternatively we can add
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
kotlinOptions {
freeCompilerArgs = freeCompilerArgs + listOf(
"-opt-in=androidx.compose.animation.ExperimentalAnimationApi",
)
}
}
but this doesn't remove the IDE underlines etc. So 👆 is not that useful.
Upvotes: 31
Reputation: 19585
With modern versions of the Kotlin Gradle multiplatform plugin, you should not manually configure this via freeCompilerArgs
. You can use a strongly typed DSL via the sourceSets.languageSettings
block. For example, with kotlin multiplatform, you might declare it as follows, using the Gradle Kotlin DSL:
sourceSets {
all {
languageSettings.apply {
optIn("kotlin.ExperimentalStdlibApi")
optIn("kotlin.experimental.ExperimentalNativeApi")
optIn("kotlinx.coroutines.ExperimentalCoroutinesApi")
}
}
}
The languageSettings
optIn
DSL can also apply to individual source sets e.g.:
commonMain {
languageSettings.apply {
...
}
}
Upvotes: 0
Reputation: 11
In an multi-module project if you want to centraly define such things, but a given library isn't used in all of the sub-modules you can do it as below - dependent on the libraries you use e.g. when you use Compose set the Compose-Opt-Ins.
In the build.gradle.kts
(root):
subprojects {
afterEvaluate {
if (configurations.any { it.incoming.dependencies.any { dep -> dep.group == "androidx.compose" } }) {
tasks.withType<KotlinCompile>().all {
kotlinOptions {
freeCompilerArgs = freeCompilerArgs + listOf(
"-opt-in=androidx.compose.ui.ExperimentalComposeUiApi",
"-opt-in=androidx.compose.foundation.ExperimentalFoundationApi",
)
}
}
}
}
Upvotes: 0
Reputation: 2837
A non-deprecated variation of @Johanns answer in Kotlin DSL (with some other annotations that I'm using):
Deprecation warning:
w: '-Xuse-experimental' is deprecated and will be removed in a future release, please use '-opt-in' instead
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile::class).all {
kotlinOptions {
freeCompilerArgs = freeCompilerArgs + listOf(
// Avoid having to stutter experimental annotations all over the codebase
"-opt-in=androidx.compose.animation.ExperimentalAnimationApi",
"-opt-in=androidx.compose.material.ExperimentalMaterialApi",
"-opt-in=androidx.compose.runtime.ExperimentalComposeApi",
"-opt-in=androidx.compose.ui.ExperimentalComposeUiApi",
"-opt-in=com.google.accompanist.navigation.material.ExperimentalMaterialNavigationApi",
"-opt-in=com.google.accompanist.pager.ExperimentalPagerApi",
"-opt-in=com.google.accompanist.permissions.ExperimentalPermissionsApi",
"-opt-in=kotlin.ExperimentalUnsignedTypes",
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
"-opt-in=kotlinx.coroutines.InternalCoroutinesApi"
)
}
}
Upvotes: 15
Reputation: 195
An alternative to avoid this situation would be to use the @OptIn instead but since only one is allowed per declaration it doesn't work out for my case with multiple experimental features.
You can put multiple experimental features comma separated into @OptIn
.
E.g. @OptIn(ExperimentalComposeUiApi::class, ExperimentalFoundationApi::class, ExperimentalMaterialApi::class)
Upvotes: 9
Reputation: 29867
I got tired of my code being polluted by all those annotations. The easiest way to get rid of them and have your code compile is just add this to your top build.gradle file - It's not exhaustive. Just add more compiler arguments for each annotation you need:
allprojects {
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
kotlinOptions {
freeCompilerArgs += [
"-Xuse-experimental=kotlin.ExperimentalUnsignedTypes",
"-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi",
"-Xuse-experimental=kotlinx.coroutines.InternalCoroutinesApi",
"-Xuse-experimental=androidx.compose.animation.ExperimentalAnimationApi",
"-Xuse-experimental=androidx.compose.ExperimentalComposeApi",
"-Xuse-experimental=androidx.compose.material.ExperimentalMaterialApi",
"-Xuse-experimental=androidx.compose.runtime.ExperimentalComposeApi",
"-Xuse-experimental=androidx.compose.ui.ExperimentalComposeUiApi",
"-Xuse-experimental=coil.annotation.ExperimentalCoilApi",
"-Xuse-experimental=kotlinx.serialization.ExperimentalSerializationApi",
"-Xuse-experimental=com.google.accompanist.pager.ExperimentalPagerApi"
]
}
}
}
Upvotes: 14