LoginSignup
2
4

More than 1 year has passed since last update.

【Android】 Jetpack ComposeをServiceで使う / Jetpack Compose on Service

Last updated at Posted at 2021-12-22

Jetpack ComposeをServiceで使おうと思ったら、いくつかエラーが発生してうまく行かなかったので、メモがてらエラーの内容と対応を記載したいと思います。

対応前のコード

下記のコードがエラーが発生してしまう対応前のコードです。
エラーに対処しつつ、Jetpack Composeが使えるところまで行きたいと思います。

BeforeのMainService.kt
class MainService : Service() {

    private val params by lazy {
        WindowManager.LayoutParams(
            WindowManager.LayoutParams.WRAP_CONTENT,
            WindowManager.LayoutParams.WRAP_CONTENT,
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
            } else {
                WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
            },
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
            PixelFormat.TRANSLUCENT,
        )
    }

    private val windowManager by lazy {
        getSystemService(Context.WINDOW_SERVICE) as WindowManager
    }

    private val composeView by lazy {
        ComposeView(this).apply {
            setContent {
                Text("Hello Compose")
            }
        }
    }

    override fun onCreate() {
        super.onCreate()
        windowManager.addView(composeView, params)
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        super.onStartCommand(intent, flags, startId)
        return START_NOT_STICKY
    }

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

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

:no_entry_sign: java.lang.IllegalStateException: ViewTreeLifecycleOwner not found from androidx.compose.ui.platform.ComposeView

↓ throw箇所

ComposeViewLifecycleOwner が設定されてないと動かないようです。
そのため、 ViewTreeLifecycleOwner.set を使って、 LifecycleOwner を設定してあげます。

LifecycleService という LifecycleOwner を実装した Service があるので、親クラスをそれに変更し、 ViewTreeLifecycleOwner.set(composeView, this)onCreate で呼ぶようにします。
onBind の実装が必須ではなくなるので削除もしました。)

変更後のコードはここをクリック
class MainService : LifecycleService() { // LifecycleServiceに変更

    private val params by lazy {
        WindowManager.LayoutParams(
            WindowManager.LayoutParams.WRAP_CONTENT,
            WindowManager.LayoutParams.WRAP_CONTENT,
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
            } else {
                WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
            },
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
            PixelFormat.TRANSLUCENT,
        )
    }

    private val windowManager by lazy {
        getSystemService(Context.WINDOW_SERVICE) as WindowManager
    }

    private val composeView by lazy {
        ComposeView(this).apply {
            setContent {
                Text("Hello Compose")
            }
        }
    }

    override fun onCreate() {
        super.onCreate()
        ViewTreeLifecycleOwner.set(composeView, this) // 追加
        windowManager.addView(composeView, params)
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        super.onStartCommand(intent, flags, startId)
        return START_NOT_STICKY
    }

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

    // onBindを削除
}

これで大丈夫かと思いきや、他のエラーが発生しました。:flushed:

:no_entry_sign: java.lang.IllegalStateException: Composed into the View which doesn't propagateViewTreeSavedStateRegistryOwner!

↓ throw箇所

ComposeView ではSavedStateRegistryOwner も必要なようです。
ViewTreeSavedStateRegistryOwner.set を使って、 SavedStateRegistryOwner を設定してあげます。

MainServiceSavedStateRegistryOwner を実装させます。
SavedStateRegistry が必要になるので、 SavedStateRegistryController をプロパティとして定義し、そこから SavedStateRegistry を返すようにしました。

変更後のコードはここをクリック
class MainService : LifecycleService(), SavedStateRegistryOwner {

    // 追加
    private val savedStateRegistryController = SavedStateRegistryController.create(this)

    private val params by lazy {
        WindowManager.LayoutParams(
            WindowManager.LayoutParams.WRAP_CONTENT,
            WindowManager.LayoutParams.WRAP_CONTENT,
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
            } else {
                WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
            },
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
            PixelFormat.TRANSLUCENT,
        )
    }

    private val windowManager by lazy {
        getSystemService(Context.WINDOW_SERVICE) as WindowManager
    }

    private val composeView by lazy {
        ComposeView(this).apply {
            setContent {
                Text("Hello Compose")
            }
        }
    }

    override fun onCreate() {
        super.onCreate()
        ViewTreeLifecycleOwner.set(composeView, this)
        ViewTreeSavedStateRegistryOwner.set(composeView, this) // 追加
        windowManager.addView(composeView, params)
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        super.onStartCommand(intent, flags, startId)
        return START_NOT_STICKY
    }

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

    // 追加
    override fun getSavedStateRegistry(): SavedStateRegistry {
        return savedStateRegistryController.savedStateRegistry
    }
}

今度こそ、と思いきやまだ駄目なようでした。:flushed:

:no_entry_sign: java.lang.IllegalStateException: You can consumeRestoredStateForKey only after super.onCreate of corresponding component

↓ throw箇所

mRestoredfalse になっているとエラーになるようなので、 performRestore を呼んで mRestoredtrue になるようにします。

これでひとまずエラーが発生せずに、動くようになりました :relaxed:

対応後のコード

Hello Compose と表示されているのが、Service上に表示されているJetpack ComposeのTextです。

tmp.gif

最終的なコードは下記の通りです。

AfterのMainService.kt
class MainService : LifecycleService(), SavedStateRegistryOwner {

    private val savedStateRegistryController = SavedStateRegistryController.create(this)

    private val params by lazy {
        WindowManager.LayoutParams(
            WindowManager.LayoutParams.WRAP_CONTENT,
            WindowManager.LayoutParams.WRAP_CONTENT,
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
            } else {
                WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
            },
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
            PixelFormat.TRANSLUCENT,
        )
    }

    private val windowManager by lazy {
        getSystemService(Context.WINDOW_SERVICE) as WindowManager
    }

    private val composeView by lazy {
        ComposeView(this).apply {
            setContent {
                Text("Hello Compose")
            }
        }
    }

    override fun onCreate() {
        super.onCreate()
        ViewTreeLifecycleOwner.set(composeView, this)
        ViewTreeSavedStateRegistryOwner.set(composeView, this)
        savedStateRegistryController.performRestore(null)
        windowManager.addView(composeView, params)
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        super.onStartCommand(intent, flags, startId)
        return START_NOT_STICKY
    }

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

    override fun getSavedStateRegistry(): SavedStateRegistry {
        return savedStateRegistryController.savedStateRegistry
    }
}

参考

2
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4