Is there a Jetpack Compose equivalent for android:keepScreenOn to keep screen alive?

Viewed 1941

I have a Composable that uses a Handler to slowly update the alpha of an image inside a composable. However, I'm seeing that the screen turns off before the animation could complete.

In XML layouts, we could keep it alive using
android:keepScreenOn
or
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)

Is there a way to do this using compose without using the wake lock permission?

4 Answers

You can use LocalContext to get activity, and it has a window on which you can apply needed flags.

In such cases, when you need to run some code on both view appearance and disappearance, DisposableEffect can be used:

@Composable
fun KeepScreenOn() {
    val context = LocalContext.current
    DisposableEffect(Unit) {
        val window = context.findActivity()?.window
        window?.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
        onDispose {
            window?.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
        }
    }
}

fun Context.findActivity(): Activity? {
    var context = this
    while (context is ContextWrapper) {
        if (context is Activity) return context
        context = context.baseContext
    }
    return null
}

Usage: when screen appears flag is set to on, and when disappears - it's cleared.

@Composable
fun Screen() {
    KeepScreenOn()
}

As @Louis CAD correctly pointed out, you can have problems if you use this "view" in many views: if one view appears that uses it, and then disappears previous views that also used it, it will reset the flag.

I haven't found a way of tracking flags state to update the view, I think @Louis CAD solution is OK until Compose have some system support.

This one should be safe from any interference if you have multiple usages in the same composition:

@Composable
fun KeepScreenOn() = AndroidView({ View(it).apply { keepScreenOn = true } })

Usage is then as simple as that:

if (screenShallBeKeptOn) {
    KeepScreenOn()
}

In a more Compose way:

@Composable
fun KeepScreenOn() {
    val currentView = LocalView.current
    DisposableEffect(Unit) {
        currentView.keepScreenOn = true
        onDispose {
            currentView.keepScreenOn = false
        }
    }
}

This will be disposed of as soon as views disappear from the composition. Usage is as simple as:

@Composable
fun Screen() {
    KeepScreenOn()
}

This is how I implemented mine

In my Composable function I have a button to activate the FLAG_KEEP_SCREEN_ON or clear FLAG_KEEP_SCREEN_ON

@Composable
fun MyButton() {
    var state by rememberSaveable {
        mutableStateOf(false)
    }

    val context = LocalContext.current

    Button(
       ...
       modifier = Modifier
            .clickable {
                state = !state
                keepScreen(state, context)
            }
       ...
     )
}

fun keepScreen(state: Boolean, context : Context) {
   val activity = context as Activity
   if(state) {
     activity.window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
   }else {
   activity.window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
   }
}

Related