Search code examples
androidandroid-jetpack-composeandroid-serviceandroid-lifecycle

how to use Jetpack Compose in Service (Floating Window)


I want to use Jetpack Compose to implement the floting window UI. But I got this error:java.lang.IllegalStateException: ViewTreeLifecycleOwner not found from androidx.compose.ui.platform.ComposeView Here are my floating window service code:

class FloatingService : Service() {
    private lateinit var windowManager: WindowManager

    private lateinit var contentView: View

    private lateinit var layoutParams: WindowManager.LayoutParams

    // init windowManager, contentView, layoutParams
    override fun onCreate() {
        super.onCreate()

        windowManager = getSystemService<WindowManager>()!!


        contentView = ComposeView(this).apply {
            setContent {
                Text(text = "Hello World")
            }
        }

        layoutParams = WindowManager.LayoutParams().apply {
            type = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
                WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
            else
                WindowManager.LayoutParams.TYPE_PHONE

            width = WindowManager.LayoutParams.WRAP_CONTENT
            height = WindowManager.LayoutParams.WRAP_CONTENT
            flags =
                WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
            x = 0
            y = 200

            format = PixelFormat.RGBA_8888 // give window transparent background

            gravity = Gravity.TOP or Gravity.END // layout right
        }
    }

    // add contentView to windowManager
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        windowManager.addView(
            contentView,
            layoutParams
        )
        return super.onStartCommand(intent, flags, startId)
    }

    override fun onDestroy() {
        super.onDestroy()
        windowManager.removeView(contentView)
    }

    override fun onBind(intent: Intent?): IBinder? {
        return null
    }
}

I am confirm it can work good when you init contentView by using Button(this). What's more, I have tried using LifecycleService which in implementation("androidx.lifecycle:lifecycle-service:2.5.1") and init ViewTreeLifecycleOwner by using ViewTreeLifecycleOwner.set(contentView, this), the result is that I got another error: java.lang.IllegalStateException: Composed into the View which doesn't propagateViewTreeSavedStateRegistryOwner!.


Solution

  • OK. Here is my own solution

    1. add this dependence:
    implementation("androidx.lifecycle:lifecycle-service:2.5.1")
    
    1. make the following changes based on the code in my question
    import android.content.Intent
    import android.view.View
    import androidx.compose.material3.Text
    import androidx.compose.ui.platform.ComposeView
    import androidx.lifecycle.LifecycleService
    import androidx.savedstate.SavedStateRegistry
    import androidx.savedstate.SavedStateRegistryController
    import androidx.savedstate.SavedStateRegistryOwner
    
    // you need extends LifecycleService and implement SavedStateRegistryOwner.
    class YourService() : LifecycleService(), SavedStateRegistryOwner {
    
        // create a SavedStateRegistryController to get SavedStateRegistry object.
        private val savedStateRegistryController = SavedStateRegistryController.create(this)
    
        private lateinit var contentView: View
    
        override fun onCreate() {
            super.onCreate()
            // init your SavedStateRegistryController
            savedStateRegistryController.performAttach() // you can ignore this line, becase performRestore method will auto call performAttach() first.
            savedStateRegistryController.performRestore(null)
    
            // configure your ComposeView
            contentView = ComposeView(this).apply {
                setViewTreeSavedStateRegistryOwner(this@YourService)
                setContent {
                    Text(text = "Hello World")
                }
            }
            ViewTreeLifecycleOwner.set(contentView, this)
    
            // init your WindowManager and LayoutParams
        }
    
        override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
            return super.onStartCommand(intent, flags, startId)
            // add your contentView to windowManager
        }
    
        override fun onDestroy() {
            super.onDestroy()
            // remove your view from your windowManager
        }
    
        // override savedStateRegistry property from SavedStateRegistryOwner interface.
        override val savedStateRegistry: SavedStateRegistry
            get() = savedStateRegistryController.savedStateRegistry
    }
    
    1. register you service and declare android.permission.SYSTEM_ALERT_WINDOW permission.