Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Jetpack Compose OverlayService. You have to have all the correct permissions granted and in your manifest, but if you do, this this will show a green box with "Hello" in it!
import android.os.Bundle
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleRegistry
import androidx.savedstate.SavedStateRegistry
import androidx.savedstate.SavedStateRegistryController
import androidx.savedstate.SavedStateRegistryOwner
internal class MyLifecycleOwner : SavedStateRegistryOwner {
private var mLifecycleRegistry: LifecycleRegistry = LifecycleRegistry(this)
private var mSavedStateRegistryController: SavedStateRegistryController = SavedStateRegistryController.create(this)
/**
* @return True if the Lifecycle has been initialized.
*/
val isInitialized: Boolean
get() = true
override fun getLifecycle(): Lifecycle {
return mLifecycleRegistry
}
fun setCurrentState(state: Lifecycle.State) {
mLifecycleRegistry.currentState = state
}
fun handleLifecycleEvent(event: Lifecycle.Event) {
mLifecycleRegistry.handleLifecycleEvent(event)
}
override fun getSavedStateRegistry(): SavedStateRegistry {
return mSavedStateRegistryController.savedStateRegistry
}
fun performRestore(savedState: Bundle?) {
mSavedStateRegistryController.performRestore(savedState)
}
fun performSave(outBundle: Bundle) {
mSavedStateRegistryController.performSave(outBundle)
}
}
import android.app.AlertDialog
import android.app.Service
import android.content.Intent
import android.graphics.PixelFormat
import android.os.Build
import android.os.IBinder
import android.util.TypedValue
import android.view.Window
import android.view.WindowManager
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material.Text
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.unit.sp
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewTreeLifecycleOwner
import androidx.lifecycle.ViewTreeViewModelStoreOwner
import androidx.savedstate.ViewTreeSavedStateRegistryOwner
import com.viatek.fitnation.echelon_android.R
class OverlayService : Service() {
val windowManager get() = getSystemService(WINDOW_SERVICE) as WindowManager
override fun onCreate() {
super.onCreate()
setTheme(R.style.ThemeOverlay_AppCompat_Light)
showOverlay()
}
private fun showOverlay() {
val layoutFlag: Int = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
} else {
WindowManager.LayoutParams.TYPE_PHONE
}
val params = WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
layoutFlag,
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT
)
val composeView = ComposeView(this)
composeView.setContent {
Text(
text = "Hello",
color = Color.Black,
fontSize = 50.sp,
modifier = Modifier
.wrapContentSize()
.background(Color.Green)
)
}
// Trick The ComposeView into thinking we are tracking lifecycle
val viewModelStore = ViewModelStore()
val lifecycleOwner = MyLifecycleOwner()
lifecycleOwner.performRestore(null)
lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
ViewTreeLifecycleOwner.set(composeView, lifecycleOwner)
ViewTreeViewModelStoreOwner.set(composeView) { viewModelStore }
ViewTreeSavedStateRegistryOwner.set(composeView, lifecycleOwner)
windowManager.addView(composeView, params)
}
override fun onBind(intent: Intent): IBinder? {
return null
}
}
@MathewSachin
Copy link

MathewSachin commented Mar 5, 2021

Beta01 also needs a compositionContext.

Adding this block of code seems to work for me for now:

val coroutineContext = AndroidUiDispatcher.CurrentThread
val runRecomposeScope = CoroutineScope(coroutineContext)
val recomposer = Recomposer(coroutineContext)
composeView.compositionContext = recomposer
runRecomposeScope.launch {
    recomposer.runRecomposeAndApplyChanges()
}

Technically, we should also cancel the runRecomposeScope when the view is removed.

@zoltish
Copy link

zoltish commented Nov 5, 2021

Thank you for this! :)

I dont know if you should provide a proper LifecycleOwner such as LifecycleService in androidx.lifecycle:lifecycle-service:2.4.0, thats what I ended up doing at least!

@handstandsam
Copy link
Author

handstandsam commented Nov 21, 2021

Thanks @MathewSachin! I just got back to this project, added your code, and it worked again now with Compose 1.0.5! 🎉

@zoltish - I can take a look at that proper lifecycle owner for the service.  Thank you!!!

@handstandsam
Copy link
Author

handstandsam commented Nov 21, 2021

I pulled in LifecycleService and extended from it. Do you have an example of what is different @zoltish? Thanks!

@zoltish
Copy link

zoltish commented Nov 22, 2021

@handstandsam Awesome! I havent digged too deep into it really - but considering that the service goes through a series of lifecycle-events, and compose-view requires a proper lifecycle-owner... It seems like a good idea to do it!

@RealMoMo
Copy link

RealMoMo commented Jan 5, 2022

Thank you for this! :)

@tberghuis
Copy link

tberghuis commented Jun 9, 2022

Thanks for this, I used this code to create a floating timer https://github.com/tberghuis/FloatingCountdownTimer

@handstandsam
Copy link
Author

handstandsam commented Jun 9, 2022

Awesome!!!

@wilinz
Copy link

wilinz commented Jun 17, 2022

The UI doesn't restructure when my mutableState changes, how to deal with this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment