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

Jetpack Composeを[Service](https://developer.android.com/guide/components/services?hl=ja)で使おうと思ったら、いくつかエラーが発生してうまく行かなかったので、メモがてらエラーの内容と対応を記載したいと思います。

# 対応前のコード

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

```kotlin: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箇所

https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/WindowRecomposer.android.kt;l=244;drc=e1b4e8644fb668de6b56b71c177bdde3e62a0e73

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

[LifecycleService](https://developer.android.com/reference/android/arch/lifecycle/LifecycleService) という `LifecycleOwner` を実装した `Service` があるので、親クラスをそれに変更し、  `ViewTreeLifecycleOwner.set(composeView, this)` を `onCreate` で呼ぶようにします。
（`onBind` の実装が必須ではなくなるので削除もしました。）

<details><summary>変更後のコードはここをクリック</summary><div>

```kotlin
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を削除
}
```

</div></details>

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

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

↓ throw箇所

https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt;l=982;drc=d593263892428f72502c3725734d664d82c01f56

**`ComposeView` では`SavedStateRegistryOwner` も必要なようです。**
`ViewTreeSavedStateRegistryOwner.set` を使って、 [SavedStateRegistryOwner](https://developer.android.com/reference/androidx/savedstate/SavedStateRegistryOwner) を設定してあげます。

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

<details><summary>変更後のコードはここをクリック</summary><div>


```kotlin
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
    }
}
```

</div></details>

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

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

↓ throw箇所

https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:savedstate/savedstate/src/main/java/androidx/savedstate/SavedStateRegistry.java;l=77;drc=b6f920cbc24f2e12ba3f881a2d61f7e845e9797e

`mRestored` が `false` になっているとエラーになるようなので、 [performRestore](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:savedstate/savedstate/src/main/java/androidx/savedstate/SavedStateRegistry.java;l=221;drc=b6f920cbc24f2e12ba3f881a2d61f7e845e9797e) を呼んで `mRestored` が `true` になるようにします。

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

# 対応後のコード

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

![tmp.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/995/224dc006-6f35-93d6-86af-0aa27c77395e.gif)

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

```kotlin: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
    }
}
```

#参考
- https://gist.github.com/handstandsam/6ecff2f39da72c0b38c07aa80bbb5a2f
  - 同じようにServiceにJetpack Composeを表示させているGistです