17

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

Johann
  • 27,536
  • 39
  • 165
  • 279
Alix
  • 2,630
  • 30
  • 72

4 Answers4

26

TLDR;

@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.

Opt-In Requirements | Kotlin

Problem

@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

Solution (fix compilation, IDE warnings)

@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.

Solution to only fix compilation

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.

okmanideep
  • 960
  • 7
  • 23
14

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"
            ]
        }
    }
}
Johann
  • 27,536
  • 39
  • 165
  • 279
  • Works well, though "use-experimental" is now deprecated. I've added my own response with a referral to this answer which deals with deprecation and is in Kotlin DSL instead of Groovy DSL – Alix Dec 04 '21 at 08:52
  • @Alix what's the replacement? – c-an Mar 24 '23 at 07:02
  • https://stackoverflow.com/a/70220590 – Alix Mar 25 '23 at 14:01
14

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"
            )
        }
    }
Aly Mobarak
  • 356
  • 2
  • 17
Alix
  • 2,630
  • 30
  • 72
9

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)

Ohlsen
  • 185
  • 1
  • 8
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Dec 09 '21 at 01:46
  • This seems to work as well, good to know! – Alix Dec 09 '21 at 09:21