17

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?

Bharadwaj Giridhar
  • 977
  • 11
  • 15

7 Answers7

27

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.

Phil Dukhov
  • 67,741
  • 15
  • 184
  • 220
  • 5
    If you have multiple `KeepScreenOn()` usages in the composition, it could lead to interference and the flag could be removed when it should not. – Louis CAD Oct 14 '21 at 08:05
  • Thank goodness Compose has rescued us from the complexity of Views. – Jeffrey Blattman Mar 15 '23 at 22:23
  • I believe we can get the current activity more concisely via LocalView.current.. : https://stackoverflow.com/a/70042305/1562087 – KenIchi Jun 16 '23 at 06:05
16

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()
}
FireZenk
  • 1,056
  • 1
  • 16
  • 28
15

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()
}
Louis CAD
  • 10,965
  • 2
  • 39
  • 58
  • Unfortunately, this answer didn't work for me. Maybe provide more details around where KeepScreenOn() should be called? – hopia Feb 05 '22 at 01:18
  • Anywhere you want to have the screen on. If it's in the composition, the screen will be kept on until the user navigates away from the app or manually turns the screen off. Note that it can't be used to wake the device. – Louis CAD Feb 05 '22 at 22:11
  • Doesn't work for me either. – Jeffrey Blattman Mar 15 '23 at 22:25
0

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

0

The code below works best for me.

@Composable
fun ScreenOnKeeper() {
    val activity = LocalContext.current as Activity

    DisposableEffect(Unit) {
        activity.window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
        onDispose {
            activity.window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
        }
    }
}

The code below didn't work when I toggle its presence (conditionally add, remove, add the component).

@Composable
fun ScreenOnKeeper() {
    val view = LocalView.current

    DisposableEffect(Unit) {
        view.keepScreenOn = true
        onDispose {
            view.keepScreenOn = false
        }
    }
}
Pavel
  • 1
0

Put this in your activity theme:

<item name="android:keepScreenOn">true</item>

Stupid, but it works.

Jeffrey Blattman
  • 22,176
  • 9
  • 79
  • 134
0

i used this solution:

@Composable
fun KeepScreenOn() {
val context = LocalContext.current
AndroidView(
       factory = {
           LinearLayout(context).apply {
               keepScreenOn = true
           }
       },
       modifier = Modifier.wrapContentSize()
   ) {}
}

and just call this fun in every composable fun that you want to prevent sleep

Mahdi Zareei
  • 1,299
  • 11
  • 18