You can find a sample project to reproduce the issue on Github
I've been trying to use Jetpack Compose for a Keyboard UI. Ultimately, When I try to inflate the Keyboard via the InputMethodService
class IMEService : InputMethodService() {
override fun onCreateInputView(): View = KeyboardView(this)
}
By using this view
class KeyboardView(context: Context) : FrameLayout(context) {
init {
val view = ComposeView(context).apply {
setContent {
Keyboard() //<- This is the actual compose UI function
}
}
addView(view)
}
}
or
class KeyboardView2 constructor(
context: Context,
) : AbstractComposeView(context) {
@Composable
override fun Content() {
Keyboard()
}
}
However, when I try to use the keyboard I get the following error
java.lang.IllegalStateException: Composed into the View which doesn't propagate ViewTreeLifecycleOwner!
at androidx.compose.ui.platform.AndroidComposeView.onAttachedToWindow(AndroidComposeView.kt:599)
at android.view.View.dispatchAttachedToWindow(View.java:19676)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3458)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3465)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3465)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3465)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3465)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3465)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3465)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3465)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2126)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1817)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7779)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1031)
at android.view.Choreographer.doCallbacks(Choreographer.java:854)
at android.view.Choreographer.doFrame(Choreographer.java:789)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1016)
at android.os.Handler.handleCallback(Handler.java:914)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:227)
at android.app.ActivityThread.main(ActivityThread.java:7582)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:953)
The official documentation states
You must attach the ComposeView to a ViewTreeLifecycleOwner. The ViewTreeLifecycleOwner allows the view to be attached and detached repeatedly while preserving the composition. ComponentActivity, FragmentActivity and AppCompatActivity are all examples of classes that implement ViewTreeLifecycleOwner
However, I cannot use ComponentActivity, FragmentActivity, or AppCompatActivity to inflate the View which calls the compose code. I became stuck with implementing ViewTreeLifecycleOwner. I don't know how to do it.
How can I use @Composable functions as an Input Method View?
Edit:
As CommonsWare suggested I used the ViewTreeLifecycleOwner.set(...) method and I also had to implement ViewModelStoreOwner and SavedStateRegistryOwner as well:
class IMEService : InputMethodService(), LifecycleOwner, ViewModelStoreOwner,
SavedStateRegistryOwner {
override fun onCreateInputView(): View {
val view = KeyboardView2(this)
ViewTreeLifecycleOwner.set(view, this)
ViewTreeViewModelStoreOwner.set(view, this)
ViewTreeSavedStateRegistryOwner.set(view, this)
return view
}
//Lifecycle Methods
private var lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(this)
override fun getLifecycle(): Lifecycle {
return lifecycleRegistry
}
private fun handleLifecycleEvent(event: Lifecycle.Event) =
lifecycleRegistry.handleLifecycleEvent(event)
override fun onCreate() {
super.onCreate()
handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
}
override fun onDestroy() {
super.onDestroy()
handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
}
//ViewModelStore Methods
private val store = ViewModelStore()
override fun getViewModelStore(): ViewModelStore = store
//SaveStateRegestry Methods
private val savedStateRegistry = SavedStateRegistryController.create(this)
override fun getSavedStateRegistry(): SavedStateRegistry = savedStateRegistry.savedStateRegistry
}
Now I get a new error
java.lang.IllegalStateException: You can consumeRestoredStateForKey only after super.onCreate of corresponding component
at androidx.savedstate.SavedStateRegistry.consumeRestoredStateForKey(SavedStateRegistry.java:77)
at androidx.compose.ui.platform.DisposableUiSavedStateRegistryKt.DisposableUiSavedStateRegistry(DisposableUiSavedStateRegistry.kt:69)
at androidx.compose.ui.platform.DisposableUiSavedStateRegistryKt.DisposableUiSavedStateRegistry(DisposableUiSavedStateRegistry.kt:44)
at androidx.compose.ui.platform.AndroidAmbientsKt.ProvideAndroidAmbients(AndroidAmbients.kt:162)
at androidx.compose.ui.platform.WrappedComposition$setContent$1$1$3.invoke(Wrapper.kt:261)
[...]
This is somehow related to the lifecycle event propagation because when I comment out the onCreate and onDestroy methods the keyboard works opens without crashes, but the keyboard is not visible
ViewTreeLifecycleOwner. My guess is that they mean you need to callViewTreeLifecycleOwner.set(), passing in theViewand aLifecycleOwner. My guess is that you will need to copy the code fromLifecycleServiceinto yours, so your service itself is aLifecycleOwnerthat you can use.LifecycleOwner.