Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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
    }
}
like image 706
ccd Avatar asked Nov 04 '20 06:11

ccd


People also ask

How do I get the activity context in compose?

Create an extension function, and call this extension function with your context like context. getActivity(). Show activity on this post. Show activity on this post.

How can I get current activity?

This example demonstrates How to get current activity name in android. Step 1 − Create a new project in Android Studio, go to File ⇒ New Project and fill all required details to create a new project. Step 2 − Add the following code to res/layout/activity_main.

How do you use compose?

Compose is a verb that means "to combine,” “to put something in order,” or “to make up." The word is used near the end of a sentence. Example: Ten rooms and three baths compose the house.

How do I create a composetutorial?

Name your app ComposeTutorial and click Finish. The default template already contains some Compose elements, but in this tutorial you will build it up step by step. First, display a “Hello world!” text by adding a text element inside the onCreate method. You do this by defining a content block, and calling the Text composable function.

How do I make a simple Android app called composetutorial?

To begin, download the most recent version of Android Studio, and create an app by selecting New Project, and under the Phone and Tablet category, select Empty Compose Activity . Name your app ComposeTutorial and click Finish. The default template already contains some Compose elements, but in this tutorial you will build it up step by step.

Can I get an activity from within a composable function?

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.

Is it possible to get the activity from the context?

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.


Video Answer


7 Answers

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.

like image 81
Jeffset Avatar answered Oct 24 '22 20:10

Jeffset


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
}
like image 44
Rajeev Shetty Avatar answered Oct 24 '22 20:10

Rajeev Shetty


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.

like image 30
Abdelilah El Aissaoui Avatar answered Oct 24 '22 19:10

Abdelilah El Aissaoui


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.

like image 24
Dima Rostopira Avatar answered Oct 24 '22 20:10

Dima Rostopira


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
} 
like image 2
Ji Sungbin Avatar answered Oct 24 '22 18:10

Ji Sungbin


This extention function allows you to specify activity you want to get:

inline fun <reified Activity : ComponentActivity> Context.getActivity(): Activity? {
    return when (this) {
        is Activity -> this
        else -> {
            var context = this
            while (context is ContextWrapper) {
                context = context.baseContext
                if (context is Activity) return context
            }
            null
        }
    }
}

Example:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent { HomeScreen() }
    }
}

@Composable
fun HomeScreen() {
    val activity = LocalContext.current.getActivity<MainActivity>()
}
like image 2
Bhniy Andrew Avatar answered Oct 24 '22 19:10

Bhniy Andrew


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.

like image 1
JIm Avatar answered Oct 24 '22 19:10

JIm