'How to get activity in compose

Is there a way to get current activity in compose function?

@Composable
fun CameraPreviewScreen() {
    val context = ContextAmbient.current
    if (ActivityCompat.checkSelfPermission(
            context,
            Manifest.permission.CAMERA
        ) != PackageManager.PERMISSION_GRANTED
    ) {
        ActivityCompat.requestPermissions(
            this, MainActivity.REQUIRED_PERMISSIONS, MainActivity.REQUEST_CODE_PERMISSIONS  // get activity for `this`
        )
        return
    }
}


Solution 1:[1]

While the previous answer (which is ContextWrapper-aware) is indeed the correct one, I'd like to provide a more idiomatic implementation to copy-paste.

fun Context.getActivity(): AppCompatActivity? = when (this) {
    is AppCompatActivity -> this
    is ContextWrapper -> baseContext.getActivity()
    else -> null
}

As ContextWrappers can't possibly wrap each other significant number of times, recursion is fine here.

Solution 2:[2]

To get the context

val context = LocalContext.current

Then get activity using the context. Create an extension function, and call this extension function with your context like context.getActivity().

fun Context.getActivity(): AppCompatActivity? {
  var currentContext = this
  while (currentContext is ContextWrapper) {
       if (currentContext is AppCompatActivity) {
            return currentContext
       }
       currentContext = currentContext.baseContext
  }
  return null
}

Solution 3:[3]

You can get the activity from your composables casting the context (I haven't found a single case where the context wasn't the activity). However, has Jim mentioned, is not a good practice to do so.

val activity = LocalContext.current as Activity

Personally I use it when I'm just playing around some code that requires the activity (permissions is a good example) but once I've got it working, I simply move it to the activity and use parameters/callback.

Edit: As mentioned in the comments, using this in production code can be dangerous, as it can crash because current is a context wrapper, my suggestion is mostly for testing code.

Solution 4:[4]

For requesting runtime permission in Jetpack Compose use Accompanist library: https://github.com/google/accompanist/tree/main/permissions

Usage example from docs:

@Composable
private fun FeatureThatRequiresCameraPermission(
    navigateToSettingsScreen: () -> Unit
) {
    // Track if the user doesn't want to see the rationale any more.
    var doNotShowRationale by rememberSaveable { mutableStateOf(false) }

    val cameraPermissionState = rememberPermissionState(android.Manifest.permission.CAMERA)
    PermissionRequired(
        permissionState = cameraPermissionState,
        permissionNotGrantedContent = {
            if (doNotShowRationale) {
                Text("Feature not available")
            } else {
                Column {
                    Text("The camera is important for this app. Please grant the permission.")
                    Spacer(modifier = Modifier.height(8.dp))
                    Row {
                        Button(onClick = { cameraPermissionState.launchPermissionRequest() }) {
                            Text("Ok!")
                        }
                        Spacer(Modifier.width(8.dp))
                        Button(onClick = { doNotShowRationale = true }) {
                            Text("Nope")
                        }
                    }
                }
            }
        },
        permissionNotAvailableContent = {
            Column {
                Text(
                    "Camera permission denied. See this FAQ with information about why we " +
                        "need this permission. Please, grant us access on the Settings screen."
                )
                Spacer(modifier = Modifier.height(8.dp))
                Button(onClick = navigateToSettingsScreen) {
                    Text("Open Settings")
                }
            }
        }
    ) {
        Text("Camera permission Granted")
    }
}

Also, if you check the source, you will find out, that Google uses same workaround as provided by Rajeev answer, so Jim's answer about bad practice is somewhat disputable.

Solution 5:[5]

Rather than casting the Context to an Activity, you can safely use it by creating a LocalActivity.

val LocalActivity = staticCompositionLocalOf<ComponentActivity> {
    noLocalProvidedFor("LocalActivity")
}

private fun noLocalProvidedFor(name: String): Nothing {
    error("CompositionLocal $name not present")
}

Usage:

CompositionLocalProvider(LocalActivity provides this) {
   val activity = LocalActivity.current
   // your content
} 

Solution 6:[6]

Getting an activity from within a Composable function is considered a bad practice, as your composables should not be tightly coupled with the rest of your app. Among other things, a tight coupling will prevent you from unit-testing your composable and generally make reuse harder.

Looking at your code, it looks like you are requesting permissions from within the composable. Again, this is not something you want to be doing inside your composable, as composable functions can run as often as every frame, which means you would keep calling that function every frame.

Instead, setup your camera permissions in your activity, and pass down (via parameters) any information that is needed by your composable in order to render pixels.

Solution 7:[7]

Below is a slight modification to @Jeffset answer since Compose activities are based off of ComponentActivity and not AppCompatActivity.

fun Context.getActivity(): ComponentActivity? = when (this) {
    is ComponentActivity -> this
    is ContextWrapper -> baseContext.findActivity()
    else -> null
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Jeffset
Solution 2 Rajeev Shetty
Solution 3
Solution 4 Dima Rostopira
Solution 5 Ji Sungbin
Solution 6 JIm
Solution 7 Val Okafor