Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handling multiple experimental annotations throughout an app

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

like image 670
Alix Avatar asked Sep 13 '25 06:09

Alix


2 Answers

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.

like image 96
okmanideep Avatar answered Sep 15 '25 20:09

okmanideep


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"
            )
        }
    }
like image 42
Alix Avatar answered Sep 15 '25 21:09

Alix